diff --git a/.gitignore b/.gitignore index 1281d8072ea..b9e91a0383b 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,6 @@ Ninja # CocoaPods generate gen/ + +# b/111916494 +default.profraw diff --git a/.travis.yml b/.travis.yml index ac1caa6aa93..fb944aa4f52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,12 @@ cache: - bundler - cocoapods +stages: + - checks + - test + - name: cocoapods_compatibility_check + if: type = cron + jobs: include: - stage: checks @@ -13,13 +19,7 @@ jobs: - brew install clang-format - brew install swiftformat script: - - ./scripts/check_whitespace.sh - - ./scripts/check_copyright.sh - - ./scripts/check_no_module_imports.sh - - ./scripts/check_test_inclusion.py - - ./scripts/style.sh test-only $TRAVIS_COMMIT_RANGE - # Google C++ style compliance - - ./scripts/lint.sh $TRAVIS_COMMIT_RANGE + - ./scripts/check.sh --test-only $TRAVIS_COMMIT_RANGE # The order of builds matters (even though they are run in parallel): # Travis will schedule them in the same order they are listed here. @@ -89,38 +89,40 @@ jobs: script: # Eliminate the one warning from BoringSSL when CocoaPods 1.6.0 is available. # The travis_wait is necessary because the command takes more than 10 minutes. - - travis_wait 45 ./scripts/if_changed.sh ./scripts/pod_lib_lint.sh FirebaseFirestore.podspec --allow-warnings --no-subspecs + - travis_wait 30 ./scripts/if_changed.sh ./scripts/pod_lib_lint.sh FirebaseFirestore.podspec --platforms=ios --allow-warnings --no-subspecs # pod lib lint to check build and warnings for static library build - only on cron jobs - stage: test + if: type = cron env: - PROJECT=Firebase PLATFORM=iOS METHOD=pod-lib-lint before_install: - - ./scripts/if_cron.sh ./scripts/install_prereqs.sh + - ./scripts/install_prereqs.sh script: - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh GoogleUtilities.podspec --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseCore.podspec --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseAnalyticsInterop.podspec --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseAuth.podspec --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseAuthInterop.podspec --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseDatabase.podspec --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseDynamicLinks.podspec --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseInstanceID.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh GoogleUtilities.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseCore.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseAnalyticsInterop.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseAuth.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseAuthInterop.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseDatabase.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseDynamicLinks.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseInstanceID.podspec --use-libraries # The Protobuf dependency of FirebaseMessaging has warnings with --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseMessaging.podspec --use-libraries --allow-warnings - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseStorage.podspec --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseInAppMessaging.podspec --use-libraries - - travis_retry ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseInAppMessagingDisplay.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseMessaging.podspec --use-libraries --allow-warnings + - travis_retry ./scripts/pod_lib_lint.sh FirebaseStorage.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseInAppMessaging.podspec --use-libraries + - travis_retry ./scripts/pod_lib_lint.sh FirebaseInAppMessagingDisplay.podspec --use-libraries - stage: test + if: type = cron env: - PROJECT=Firestore PLATFORM=iOS METHOD=pod-lib-lint before_install: - - ./scripts/if_cron.sh ./scripts/install_prereqs.sh + - ./scripts/install_prereqs.sh script: # TBD - non-portable path warnings # The travis_wait is necessary because the command takes more than 10 minutes. - - travis_wait 45 ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseFirestore.podspec --use-libraries --allow-warnings --no-subspecs + - travis_wait 45 ./scripts/pod_lib_lint.sh FirebaseFirestore.podspec --use-libraries --allow-warnings --no-subspecs # GoogleDataTransport unit tests and pod linting using the default Xcode version. - stage: test @@ -144,12 +146,13 @@ jobs: # Daily test for symbol collisions between Firebase and CocoaPods. - stage: test + if: type = cron env: - PROJECT=SymbolCollision PLATFORM=iOS METHOD=xcodebuild before_install: - - ./scripts/if_cron.sh ./scripts/install_prereqs.sh + - ./scripts/install_prereqs.sh script: - - travis_retry ./scripts/if_cron.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD + - travis_retry ./scripts/build.sh $PROJECT $PLATFORM $METHOD # Alternative platforms @@ -181,6 +184,22 @@ jobs: script: - travis_retry ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM + - stage: test + env: + - PROJECT=Firestore PLATFORM=macOS METHOD=xcodebuild + before_install: + - ./scripts/if_changed.sh ./scripts/install_prereqs.sh + script: + - travis_retry ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM + + - stage: test + env: + - PROJECT=Firestore PLATFORM=tvOS METHOD=xcodebuild + before_install: + - ./scripts/if_changed.sh ./scripts/install_prereqs.sh + script: + - travis_retry ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM + # Firestore sanitizers - stage: test @@ -219,16 +238,57 @@ jobs: script: - travis_retry ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD + # Validate Cocoapods configurations + # This may take long time, so we would like to run it only once all other tests pass + # Validate Cocoapods 1.7.0 compatibility + - stage: cocoapods_compatibility_check + env: + - POD_CONFIG_DIR=Cocoapods1_7_0_multiprojects_frameworks + script: + - travis_retry ./CocoapodsIntegrationTest/scripts/build_with_environment.sh --gemfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Gemfile --podfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Podfile + + - stage: cocoapods_compatibility_check + env: + - POD_CONFIG_DIR=Cocoapods1_7_0_frameworks + script: + - travis_retry ./CocoapodsIntegrationTest/scripts/build_with_environment.sh --gemfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Gemfile --podfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Podfile + + - stage: cocoapods_compatibility_check + env: + - POD_CONFIG_DIR=Cocoapods1_7_0_multiprojects_staticLibs + script: + - travis_retry ./CocoapodsIntegrationTest/scripts/build_with_environment.sh --gemfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Gemfile --podfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Podfile + + - stage: cocoapods_compatibility_check + env: + - POD_CONFIG_DIR=Cocoapods1_7_0_staticLibs + script: + - travis_retry ./CocoapodsIntegrationTest/scripts/build_with_environment.sh --gemfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Gemfile --podfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Podfile + + # Validate Cocoapods 1.6.1 compatibility + - stage: cocoapods_compatibility_check + env: + - POD_CONFIG_DIR=Cocoapods1_6_1_frameworks + script: + - travis_retry ./CocoapodsIntegrationTest/scripts/build_with_environment.sh --gemfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Gemfile --podfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Podfile + + - stage: cocoapods_compatibility_check + env: + - POD_CONFIG_DIR=Cocoapods1_6_1_staticLibs + script: + - travis_retry ./CocoapodsIntegrationTest/scripts/build_with_environment.sh --gemfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Gemfile --podfile=./CocoapodsIntegrationTest/TestEnvironments/${POD_CONFIG_DIR}/Podfile + allow_failures: # Run fuzz tests only on cron jobs. - stage: test + if: type = cron env: - PROJECT=Firestore PLATFORM=iOS METHOD=fuzz before_install: - - ./scripts/if_cron.sh ./scripts/install_prereqs.sh + - ./scripts/install_prereqs.sh script: # The travis_wait is necessary because fuzzing runs for 40 minutes. - - travis_wait 45 ./scripts/if_cron.sh ./scripts/fuzzing_ci.sh + - travis_wait 45 ./scripts/fuzzing_ci.sh # TODO(varconst): UBSan for CMake. UBSan failures are non-fatal by default, # need to make them fatal for the purposes of the test run. @@ -242,6 +302,8 @@ jobs: - PROJECT=Firestore PLATFORM=iOS METHOD=xcodebuild SANITIZERS=asan - env: - PROJECT=Firestore PLATFORM=iOS METHOD=xcodebuild SANITIZERS=tsan + - env: + - PROJECT=InAppMessaging PLATFORM=iOS METHOD=xcodebuild # TODO(varconst): enable if it's possible to make this flag work on build # stages. It's supposed to avoid waiting for jobs that are allowed to fail diff --git a/Carthage.md b/Carthage.md index 1ee4f8da2e7..9490de52bde 100644 --- a/Carthage.md +++ b/Carthage.md @@ -33,17 +33,16 @@ binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseABTestingBinary.j binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAdMobBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAnalyticsBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAuthBinary.json" -binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseCrashBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseDatabaseBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseDynamicLinksBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseFirestoreBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseFunctionsBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseInAppMessagingBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseInAppMessagingDisplayBinary.json" -binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseInvitesBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseMessagingBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseMLModelInterpreterBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseMLNLLanguageIDBinary.json" +binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseMLNLSmartReplyBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseMLNaturalLanguageBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseMLVisionBarcodeModelBinary.json" binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseMLVisionBinary.json" @@ -66,10 +65,6 @@ binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseStorageBinary.jso `Copy Bundle Resources` Build Phase : - For Firestore: - ./Carthage/Build/iOS/FirebaseFirestore.framework/gRPCCertificates.bundle - - For Invites: - - ./Carthage/Build/iOS/FirebaseInvites.framework/GoogleSignIn.bundle - - ./Carthage/Build/iOS/FirebaseInvites.framework/GPPACLPickerResources.bundle - - ./Carthage/Build/iOS/FirebaseInvites.framework/GINInviteResources.bundle - For FirebaseMLVisionFaceModel: - ./Carthage/Build/iOS/FaceDetector.framework/GoogleMVFaceDetectorResources.bundle - For FirebaseMLVisionTextModel: diff --git a/CocoapodsIntegrationTest/CocoapodsIntegrationTest.xcodeproj/project.pbxproj b/CocoapodsIntegrationTest/CocoapodsIntegrationTest.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..c1224d0f529 --- /dev/null +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest.xcodeproj/project.pbxproj @@ -0,0 +1,411 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 9A9CEFAD22564DAB00C32C5B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CEFAC22564DAB00C32C5B /* AppDelegate.m */; }; + 9A9CEFB022564DAB00C32C5B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CEFAF22564DAB00C32C5B /* ViewController.m */; }; + 9A9CEFB322564DAB00C32C5B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A9CEFB122564DAB00C32C5B /* Main.storyboard */; }; + 9A9CEFB522564DAC00C32C5B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A9CEFB422564DAC00C32C5B /* Assets.xcassets */; }; + 9A9CEFB822564DAC00C32C5B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A9CEFB622564DAC00C32C5B /* LaunchScreen.storyboard */; }; + 9A9CEFBB22564DAC00C32C5B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CEFBA22564DAC00C32C5B /* main.m */; }; + A1D7F89167D4EDC7356BA0F1 /* Pods_CocoapodsIntegrationTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B465C9A8AC12C2E1B44B6A6 /* Pods_CocoapodsIntegrationTest.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 8B465C9A8AC12C2E1B44B6A6 /* Pods_CocoapodsIntegrationTest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CocoapodsIntegrationTest.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A9CEFA822564DAB00C32C5B /* CocoapodsIntegrationTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CocoapodsIntegrationTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A9CEFAB22564DAB00C32C5B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 9A9CEFAC22564DAB00C32C5B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9A9CEFAE22564DAB00C32C5B /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 9A9CEFAF22564DAB00C32C5B /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 9A9CEFB222564DAB00C32C5B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 9A9CEFB422564DAC00C32C5B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 9A9CEFB722564DAC00C32C5B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 9A9CEFB922564DAC00C32C5B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9A9CEFBA22564DAC00C32C5B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + B60CFECB6829D89F305E8225 /* Pods-CocoapodsIntegrationTest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CocoapodsIntegrationTest.release.xcconfig"; path = "Target Support Files/Pods-CocoapodsIntegrationTest/Pods-CocoapodsIntegrationTest.release.xcconfig"; sourceTree = ""; }; + DEA28D2A6F2A50E15ADA23A7 /* Pods-CocoapodsIntegrationTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CocoapodsIntegrationTest.debug.xcconfig"; path = "Target Support Files/Pods-CocoapodsIntegrationTest/Pods-CocoapodsIntegrationTest.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 9A9CEFA522564DAB00C32C5B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A1D7F89167D4EDC7356BA0F1 /* Pods_CocoapodsIntegrationTest.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5262DE71BE536EC6B8E91815 /* Pods */ = { + isa = PBXGroup; + children = ( + DEA28D2A6F2A50E15ADA23A7 /* Pods-CocoapodsIntegrationTest.debug.xcconfig */, + B60CFECB6829D89F305E8225 /* Pods-CocoapodsIntegrationTest.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9A9CEF9F22564DAB00C32C5B = { + isa = PBXGroup; + children = ( + 9A9CEFAA22564DAB00C32C5B /* CocoapodsIntegrationTest */, + 9A9CEFA922564DAB00C32C5B /* Products */, + 5262DE71BE536EC6B8E91815 /* Pods */, + A9C0E60CE8FE7029246BCCBF /* Frameworks */, + ); + sourceTree = ""; + }; + 9A9CEFA922564DAB00C32C5B /* Products */ = { + isa = PBXGroup; + children = ( + 9A9CEFA822564DAB00C32C5B /* CocoapodsIntegrationTest.app */, + ); + name = Products; + sourceTree = ""; + }; + 9A9CEFAA22564DAB00C32C5B /* CocoapodsIntegrationTest */ = { + isa = PBXGroup; + children = ( + 9A9CEFAB22564DAB00C32C5B /* AppDelegate.h */, + 9A9CEFAC22564DAB00C32C5B /* AppDelegate.m */, + 9A9CEFAE22564DAB00C32C5B /* ViewController.h */, + 9A9CEFAF22564DAB00C32C5B /* ViewController.m */, + 9A9CEFB122564DAB00C32C5B /* Main.storyboard */, + 9A9CEFB422564DAC00C32C5B /* Assets.xcassets */, + 9A9CEFB622564DAC00C32C5B /* LaunchScreen.storyboard */, + 9A9CEFB922564DAC00C32C5B /* Info.plist */, + 9A9CEFBA22564DAC00C32C5B /* main.m */, + ); + path = CocoapodsIntegrationTest; + sourceTree = ""; + }; + A9C0E60CE8FE7029246BCCBF /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8B465C9A8AC12C2E1B44B6A6 /* Pods_CocoapodsIntegrationTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 9A9CEFA722564DAB00C32C5B /* CocoapodsIntegrationTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A9CEFBE22564DAC00C32C5B /* Build configuration list for PBXNativeTarget "CocoapodsIntegrationTest" */; + buildPhases = ( + 21DA73D41725957D85E38FC2 /* [CP] Check Pods Manifest.lock */, + 9A9CEFA422564DAB00C32C5B /* Sources */, + 9A9CEFA522564DAB00C32C5B /* Frameworks */, + 9A9CEFA622564DAB00C32C5B /* Resources */, + 840F09F7845F2F1AB96D6F7E /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CocoapodsIntegrationTest; + productName = CocoapodsIntegrationTest; + productReference = 9A9CEFA822564DAB00C32C5B /* CocoapodsIntegrationTest.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9A9CEFA022564DAB00C32C5B /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1010; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 9A9CEFA722564DAB00C32C5B = { + CreatedOnToolsVersion = 10.1; + }; + }; + }; + buildConfigurationList = 9A9CEFA322564DAB00C32C5B /* Build configuration list for PBXProject "CocoapodsIntegrationTest" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 9A9CEF9F22564DAB00C32C5B; + productRefGroup = 9A9CEFA922564DAB00C32C5B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 9A9CEFA722564DAB00C32C5B /* CocoapodsIntegrationTest */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 9A9CEFA622564DAB00C32C5B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A9CEFB822564DAC00C32C5B /* LaunchScreen.storyboard in Resources */, + 9A9CEFB522564DAC00C32C5B /* Assets.xcassets in Resources */, + 9A9CEFB322564DAB00C32C5B /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 21DA73D41725957D85E38FC2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-CocoapodsIntegrationTest-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 840F09F7845F2F1AB96D6F7E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-CocoapodsIntegrationTest/Pods-CocoapodsIntegrationTest-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-CocoapodsIntegrationTest/Pods-CocoapodsIntegrationTest-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CocoapodsIntegrationTest/Pods-CocoapodsIntegrationTest-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 9A9CEFA422564DAB00C32C5B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A9CEFB022564DAB00C32C5B /* ViewController.m in Sources */, + 9A9CEFBB22564DAC00C32C5B /* main.m in Sources */, + 9A9CEFAD22564DAB00C32C5B /* AppDelegate.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 9A9CEFB122564DAB00C32C5B /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 9A9CEFB222564DAB00C32C5B /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 9A9CEFB622564DAC00C32C5B /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 9A9CEFB722564DAC00C32C5B /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 9A9CEFBC22564DAC00C32C5B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 9A9CEFBD22564DAC00C32C5B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 9A9CEFBF22564DAC00C32C5B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DEA28D2A6F2A50E15ADA23A7 /* Pods-CocoapodsIntegrationTest.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = CocoapodsIntegrationTest/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = google.com.CocoapodsIntegrationTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 9A9CEFC022564DAC00C32C5B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B60CFECB6829D89F305E8225 /* Pods-CocoapodsIntegrationTest.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = CocoapodsIntegrationTest/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = google.com.CocoapodsIntegrationTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 9A9CEFA322564DAB00C32C5B /* Build configuration list for PBXProject "CocoapodsIntegrationTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A9CEFBC22564DAC00C32C5B /* Debug */, + 9A9CEFBD22564DAC00C32C5B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9A9CEFBE22564DAC00C32C5B /* Build configuration list for PBXNativeTarget "CocoapodsIntegrationTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A9CEFBF22564DAC00C32C5B /* Debug */, + 9A9CEFC022564DAC00C32C5B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 9A9CEFA022564DAB00C32C5B /* Project object */; +} diff --git a/CocoapodsIntegrationTest/CocoapodsIntegrationTest.xcodeproj/xcshareddata/xcschemes/CocoapodsIntegrationTest.xcscheme b/CocoapodsIntegrationTest/CocoapodsIntegrationTest.xcodeproj/xcshareddata/xcschemes/CocoapodsIntegrationTest.xcscheme new file mode 100644 index 00000000000..c7f4d3c3616 --- /dev/null +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest.xcodeproj/xcshareddata/xcschemes/CocoapodsIntegrationTest.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CocoapodsIntegrationTest/CocoapodsIntegrationTest/AppDelegate.h b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/AppDelegate.h new file mode 100644 index 00000000000..b0b8e06e84c --- /dev/null +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/AppDelegate.h @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface AppDelegate : UIResponder + +@property(strong, nonatomic) UIWindow *window; + +@end diff --git a/GoogleUtilities/Logger/GULASLLogger.h b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/AppDelegate.m similarity index 77% rename from GoogleUtilities/Logger/GULASLLogger.h rename to CocoapodsIntegrationTest/CocoapodsIntegrationTest/AppDelegate.m index d7a0bc563fd..ae0efdf729f 100644 --- a/GoogleUtilities/Logger/GULASLLogger.h +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/AppDelegate.m @@ -14,13 +14,13 @@ * limitations under the License. */ -#import +#import "AppDelegate.h" +#import -#import +@interface AppDelegate () -NS_ASSUME_NONNULL_BEGIN - -@interface GULASLLogger : NSObject @end -NS_ASSUME_NONNULL_END +@implementation AppDelegate + +@end diff --git a/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Assets.xcassets/AppIcon.appiconset/Contents.json b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..d8db8d65fd7 --- /dev/null +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/macOS_example/Assets.xcassets/Contents.json b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Assets.xcassets/Contents.json similarity index 100% rename from Firestore/Example/App/macOS_example/Assets.xcassets/Contents.json rename to CocoapodsIntegrationTest/CocoapodsIntegrationTest/Assets.xcassets/Contents.json diff --git a/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Base.lproj/LaunchScreen.storyboard b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000000..bfa36129419 --- /dev/null +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Base.lproj/Main.storyboard b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Base.lproj/Main.storyboard new file mode 100644 index 00000000000..942f0bc452d --- /dev/null +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Info.plist b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Info.plist new file mode 100644 index 00000000000..16be3b68112 --- /dev/null +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/CocoapodsIntegrationTest/CocoapodsIntegrationTest/ViewController.h b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/ViewController.h new file mode 100644 index 00000000000..7f02e1a5218 --- /dev/null +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/ViewController.h @@ -0,0 +1,21 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface ViewController : UIViewController + +@end diff --git a/GoogleUtilities/Logger/GULOSLogger.h b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/ViewController.m similarity index 73% rename from GoogleUtilities/Logger/GULOSLogger.h rename to CocoapodsIntegrationTest/CocoapodsIntegrationTest/ViewController.m index 3a3d0962597..a4696c81665 100644 --- a/GoogleUtilities/Logger/GULOSLogger.h +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/ViewController.m @@ -14,14 +14,12 @@ * limitations under the License. */ -#import +#import "ViewController.h" -#import -#import +@interface ViewController () -NS_ASSUME_NONNULL_BEGIN - -@interface GULOSLogger : NSObject @end -NS_ASSUME_NONNULL_END +@implementation ViewController + +@end diff --git a/CocoapodsIntegrationTest/CocoapodsIntegrationTest/main.m b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/main.m new file mode 100644 index 00000000000..efb3cc9e634 --- /dev/null +++ b/CocoapodsIntegrationTest/CocoapodsIntegrationTest/main.m @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/CocoapodsIntegrationTest/Gemfile b/CocoapodsIntegrationTest/Gemfile new file mode 100644 index 00000000000..a4698322428 --- /dev/null +++ b/CocoapodsIntegrationTest/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem 'cocoapods', "~>1.7.0.beta" diff --git a/CocoapodsIntegrationTest/Gemfile.lock b/CocoapodsIntegrationTest/Gemfile.lock new file mode 100644 index 00000000000..bc83f51078c --- /dev/null +++ b/CocoapodsIntegrationTest/Gemfile.lock @@ -0,0 +1,76 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.0) + activesupport (4.2.11.1) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + atomos (0.1.3) + claide (1.0.2) + cocoapods (1.7.0.beta.3) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.7.0.beta.3) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.2.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.6.6) + nap (~> 1.0) + ruby-macho (~> 1.4) + xcodeproj (>= 1.8.2, < 2.0) + cocoapods-core (1.7.0.beta.3) + activesupport (>= 4.0.2, < 6) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.2.2) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.3.1) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.1.0) + colored2 (3.1.2) + concurrent-ruby (1.1.5) + escape (0.0.4) + fourflusher (2.2.0) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + minitest (5.11.3) + molinillo (0.6.6) + nanaimo (0.2.6) + nap (1.1.0) + netrc (0.11.0) + ruby-macho (1.4.0) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + xcodeproj (1.8.2) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods (~> 1.7.0.beta) + +BUNDLED WITH + 1.17.1 diff --git a/CocoapodsIntegrationTest/Podfile b/CocoapodsIntegrationTest/Podfile new file mode 100644 index 00000000000..ed45548a20d --- /dev/null +++ b/CocoapodsIntegrationTest/Podfile @@ -0,0 +1,26 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'CocoapodsIntegrationTest' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + pod 'FirebaseCore', :path => '../' + pod 'FirebaseAuth', :path => '../' + pod 'FirebaseCore', :path => '../' + pod 'FirebaseDatabase', :path => '../' + pod 'FirebaseDynamicLinks', :path => '../' + pod 'FirebaseFirestore', :path => '../' + pod 'FirebaseFunctions', :path => '../' + pod 'FirebaseInAppMessaging', :path => '../' + pod 'FirebaseInstanceID', :path => '../' + pod 'FirebaseMessaging', :path => '../' + pod 'FirebaseStorage', :path => '../' + pod 'GoogleUtilities', :path => '../' +end + +# Using the new speed-enhancing features available with CocoaPods 1.7+ +# [sudo] gem install cocoapods --pre +install! 'cocoapods', + :generate_multiple_pod_projects => true, + :incremental_installation => true diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_frameworks/Gemfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_frameworks/Gemfile new file mode 100644 index 00000000000..7d579adf0f1 --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_frameworks/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem 'cocoapods', "=1.6.1" diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_frameworks/Podfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_frameworks/Podfile new file mode 100644 index 00000000000..ba9f0da36ac --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_frameworks/Podfile @@ -0,0 +1,19 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'CocoapodsIntegrationTest' do + use_frameworks! + + pod 'FirebaseCore', :path => '../' + pod 'FirebaseAuth', :path => '../' + pod 'FirebaseCore', :path => '../' + pod 'FirebaseDatabase', :path => '../' + pod 'FirebaseDynamicLinks', :path => '../' + pod 'FirebaseFirestore', :path => '../' + pod 'FirebaseFunctions', :path => '../' + pod 'FirebaseInAppMessaging', :path => '../' + pod 'FirebaseInstanceID', :path => '../' + pod 'FirebaseMessaging', :path => '../' + pod 'FirebaseStorage', :path => '../' + pod 'GoogleUtilities', :path => '../' +end diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_staticLibs/Gemfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_staticLibs/Gemfile new file mode 100644 index 00000000000..7d579adf0f1 --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_staticLibs/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem 'cocoapods', "=1.6.1" diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_staticLibs/Podfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_staticLibs/Podfile new file mode 100644 index 00000000000..5d49a5ba8dc --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_6_1_staticLibs/Podfile @@ -0,0 +1,17 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'CocoapodsIntegrationTest' do + pod 'FirebaseCore', :path => '../' + pod 'FirebaseAuth', :path => '../' + pod 'FirebaseCore', :path => '../' + pod 'FirebaseDatabase', :path => '../' + pod 'FirebaseDynamicLinks', :path => '../' + pod 'FirebaseFirestore', :path => '../' + pod 'FirebaseFunctions', :path => '../' + pod 'FirebaseInAppMessaging', :path => '../' + pod 'FirebaseInstanceID', :path => '../' + pod 'FirebaseMessaging', :path => '../' + pod 'FirebaseStorage', :path => '../' + pod 'GoogleUtilities', :path => '../' +end diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_frameworks/Gemfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_frameworks/Gemfile new file mode 100644 index 00000000000..a4698322428 --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_frameworks/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem 'cocoapods', "~>1.7.0.beta" diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_frameworks/Podfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_frameworks/Podfile new file mode 100644 index 00000000000..1ad0177e5d0 --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_frameworks/Podfile @@ -0,0 +1,19 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'CocoapodsIntegrationTest' do + use_frameworks! + + pod 'FirebaseCore', :path => '../' + pod 'FirebaseAuth', :path => '../' + pod 'FirebaseCore', :path => '../' + pod 'FirebaseDatabase', :path => '../' + pod 'FirebaseDynamicLinks', :path => '../' + pod 'FirebaseFirestore', :path => '../' + pod 'FirebaseFunctions', :path => '../' + pod 'FirebaseInAppMessaging', :path => '../' + pod 'FirebaseInstanceID', :path => '../' + pod 'FirebaseMessaging', :path => '../' + pod 'FirebaseStorage', :path => '../' + pod 'GoogleUtilities', :path => '../' +end \ No newline at end of file diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_frameworks/Gemfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_frameworks/Gemfile new file mode 100644 index 00000000000..a4698322428 --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_frameworks/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem 'cocoapods', "~>1.7.0.beta" diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_frameworks/Podfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_frameworks/Podfile new file mode 100644 index 00000000000..ed45548a20d --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_frameworks/Podfile @@ -0,0 +1,26 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'CocoapodsIntegrationTest' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + pod 'FirebaseCore', :path => '../' + pod 'FirebaseAuth', :path => '../' + pod 'FirebaseCore', :path => '../' + pod 'FirebaseDatabase', :path => '../' + pod 'FirebaseDynamicLinks', :path => '../' + pod 'FirebaseFirestore', :path => '../' + pod 'FirebaseFunctions', :path => '../' + pod 'FirebaseInAppMessaging', :path => '../' + pod 'FirebaseInstanceID', :path => '../' + pod 'FirebaseMessaging', :path => '../' + pod 'FirebaseStorage', :path => '../' + pod 'GoogleUtilities', :path => '../' +end + +# Using the new speed-enhancing features available with CocoaPods 1.7+ +# [sudo] gem install cocoapods --pre +install! 'cocoapods', + :generate_multiple_pod_projects => true, + :incremental_installation => true diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_staticLibs/Gemfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_staticLibs/Gemfile new file mode 100644 index 00000000000..a4698322428 --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_staticLibs/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem 'cocoapods', "~>1.7.0.beta" diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_staticLibs/Podfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_staticLibs/Podfile new file mode 100644 index 00000000000..c20dc638114 --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_multiprojects_staticLibs/Podfile @@ -0,0 +1,23 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'CocoapodsIntegrationTest' do + pod 'FirebaseCore', :path => '../' + pod 'FirebaseAuth', :path => '../' + pod 'FirebaseCore', :path => '../' + pod 'FirebaseDatabase', :path => '../' + pod 'FirebaseDynamicLinks', :path => '../' + pod 'FirebaseFirestore', :path => '../' + pod 'FirebaseFunctions', :path => '../' + pod 'FirebaseInAppMessaging', :path => '../' + pod 'FirebaseInstanceID', :path => '../' + pod 'FirebaseMessaging', :path => '../' + pod 'FirebaseStorage', :path => '../' + pod 'GoogleUtilities', :path => '../' +end + +# Using the new speed-enhancing features available with CocoaPods 1.7+ +# [sudo] gem install cocoapods --pre +install! 'cocoapods', + :generate_multiple_pod_projects => true, + :incremental_installation => true diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_staticLibs/Gemfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_staticLibs/Gemfile new file mode 100644 index 00000000000..a4698322428 --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_staticLibs/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem 'cocoapods', "~>1.7.0.beta" diff --git a/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_staticLibs/Podfile b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_staticLibs/Podfile new file mode 100644 index 00000000000..5d49a5ba8dc --- /dev/null +++ b/CocoapodsIntegrationTest/TestEnvironments/Cocoapods1_7_0_staticLibs/Podfile @@ -0,0 +1,17 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'CocoapodsIntegrationTest' do + pod 'FirebaseCore', :path => '../' + pod 'FirebaseAuth', :path => '../' + pod 'FirebaseCore', :path => '../' + pod 'FirebaseDatabase', :path => '../' + pod 'FirebaseDynamicLinks', :path => '../' + pod 'FirebaseFirestore', :path => '../' + pod 'FirebaseFunctions', :path => '../' + pod 'FirebaseInAppMessaging', :path => '../' + pod 'FirebaseInstanceID', :path => '../' + pod 'FirebaseMessaging', :path => '../' + pod 'FirebaseStorage', :path => '../' + pod 'GoogleUtilities', :path => '../' +end diff --git a/CocoapodsIntegrationTest/scripts/build_all_environments.sh b/CocoapodsIntegrationTest/scripts/build_all_environments.sh new file mode 100755 index 00000000000..b992b76fae0 --- /dev/null +++ b/CocoapodsIntegrationTest/scripts/build_all_environments.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# Copyright 2019 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The script uses combinations of Gemfile/Podfile to validate Firebase +# compatibility with Cocoapods versions and settings. +# To add a new configuration: +# - create a new directory in `CocoapodsIntegrationTest/TestEnvironments` +# - place Gemfile and Pod file to the created directory +# The script attempts to build the test project for each configuration from +# `CocoapodsIntegrationTest/TestEnvironments`. + +set -euo pipefail + +scriptsDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +buildScripts="$( realpath ${scriptsDir}/build_with_environment.sh )" +environmentsDir="$( realpath ${scriptsDir}/../TestEnvironments )" + +echo "Dir: ${environmentsDir}" + +pushd "${environmentsDir}" + +for environmentDir in `find . -type d -mindepth 1 -maxdepth 1` +do + echo "" + echo "--- Build for environment from ${environmentDir} ---" + source $buildScripts --gemfile="${environmentDir}/Gemfile" --podfile="${environmentDir}/Podfile" + echo "--- Build for environment from ${environmentDir} finished ---" +done + +popd \ No newline at end of file diff --git a/CocoapodsIntegrationTest/scripts/build_with_environment.sh b/CocoapodsIntegrationTest/scripts/build_with_environment.sh new file mode 100755 index 00000000000..efa366027f0 --- /dev/null +++ b/CocoapodsIntegrationTest/scripts/build_with_environment.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# Copyright 2019 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The script uses combination of Gemfile/Podfile to validate Firebase +# compatibility with Cocoapods versions and settings. +# See `printUsage` for usage. + +set -euo pipefail + +# +function runXcodebuild() { + parameters=( + -workspace 'CocoapodsIntegrationTest.xcworkspace' + -scheme 'CocoapodsIntegrationTest' + -sdk 'iphonesimulator' + -destination 'platform=iOS Simulator,name=iPhone 7' + CODE_SIGNING_REQUIRED=NO + clean + build + ) + + echo xcodebuild "${parameters[@]}" + xcodebuild "${parameters[@]}" | xcpretty; result=$? +} + +# Configures bundler environment using Gemfile at the specified path. +function prepareBundle() { + cp -f "$@" ./Gemfile + + # Travis sets this variable. We should unset it otherwise `bundle exec` will + # use the Gemfile by the path from the variable. + unset BUNDLE_GEMFILE + + bundle update +} + +# Updates Cocoapods using Podfile at the specified path. +function prepareCocoapods() { + cp -f "$@" ./Podfile + echo "Cocoapods version: $(bundle exec pod --version)" + bundle exec pod deintegrate + bundle exec pod update +} + +function printUsage() { + echo "USAGE: build_with_environment.sh --gemfile= --podfile=" +} + +for i in "$@" +do + case $i in + --gemfile=*) + GEMFILE="${i#*=}" + shift + ;; + --podfile=*) + PODFILE="${i#*=}" + shift + ;; + *) + printUsage + exit -1 + ;; + esac +done + +if [ -z ${GEMFILE+x} ]; then + echo "--gemfile= is a required parameter" + exit -1 +fi + +if [ -z ${PODFILE+x} ]; then + echo "--podfile= is a required parameter" + exit -1 +fi + +# Convert path to absolute one in case the script is run from another directory. +RESOLVED_GEMFILE="$(realpath ${GEMFILE})" +RESLOVED_PODFILE="$(realpath ${PODFILE})" + +echo "Gemfile = ${RESOLVED_GEMFILE}" +echo "Podfile = ${RESLOVED_PODFILE}" + +# Make sure we build from the project root dir. +scriptDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +pushd "${scriptDir}/.." + +prepareBundle "${RESOLVED_GEMFILE}" +prepareCocoapods "${RESLOVED_PODFILE}" +runXcodebuild + +# Recover original directory just in case +popd diff --git a/Example/Auth/ApiTests/FacebookAuthTests.m b/Example/Auth/ApiTests/FacebookAuthTests.m index cb74fe71461..b21c47e1650 100644 --- a/Example/Auth/ApiTests/FacebookAuthTests.m +++ b/Example/Auth/ApiTests/FacebookAuthTests.m @@ -54,7 +54,7 @@ - (void)testSignInWithFaceboook { XCTestExpectation *expectation = [self expectationWithDescription:@"Facebook sign-in finished."]; [auth signInWithCredential:credential - completion:^(FIRUser *user, NSError *error) { + completion:^(FIRAuthDataResult *result, NSError *error) { if (error) { NSLog(@"Facebook sign in error: %@", error); } @@ -94,7 +94,7 @@ - (void)testLinkAnonymousAccountToFacebookAccount { XCTestExpectation *expectation = [self expectationWithDescription:@"Facebook linking finished."]; [auth.currentUser linkWithCredential:credential - completion:^(FIRUser *user, NSError *error) { + completion:^(FIRAuthDataResult *result, NSError *error) { if (error) { NSLog(@"Link to Facebok error: %@", error); } diff --git a/Example/Auth/ApiTests/GoogleAuthTests.m b/Example/Auth/ApiTests/GoogleAuthTests.m index 4611a1b8edf..0d176e02f39 100644 --- a/Example/Auth/ApiTests/GoogleAuthTests.m +++ b/Example/Auth/ApiTests/GoogleAuthTests.m @@ -48,7 +48,7 @@ - (void)testSignInWithGoogle { XCTestExpectation *expectation = [self expectationWithDescription:@"Signing in with Google finished."]; [auth signInWithCredential:credential - completion:^(FIRUser *user, NSError *error) { + completion:^(FIRAuthDataResult *result, NSError *error) { if (error) { NSLog(@"Signing in with Google had error: %@", error); } diff --git a/Example/Auth/ApiTests/GoogleAuthTests.swift b/Example/Auth/ApiTests/GoogleAuthTests.swift index 9a2126ca614..c0f6ecb5fea 100644 --- a/Example/Auth/ApiTests/GoogleAuthTests.swift +++ b/Example/Auth/ApiTests/GoogleAuthTests.swift @@ -35,7 +35,7 @@ class GoogleAuthTestsSwift: FIRAuthApiTestsBase { let credential = GoogleAuthProvider.credential(withIDToken: googleIdToken, accessToken: googleAccessToken) let expectation = self.expectation(description: "Signing in with Google finished.") - auth.signInAndRetrieveData(with: credential) { _, error in + auth.signIn(with: credential) { _, error in if error != nil { print("Signing in with Google had error: %@", error!) } diff --git a/Example/Auth/Sample/MainViewController.m b/Example/Auth/Sample/MainViewController.m index 278425def00..a4c17c09923 100644 --- a/Example/Auth/Sample/MainViewController.m +++ b/Example/Auth/Sample/MainViewController.m @@ -732,6 +732,16 @@ @implementation MainViewController { @brief The continue URL to be used in the next action code request. */ NSURL *_actionCodeContinueURL; + + /** @var _googleOAuthProvider + @brief OAuth provider instance for Google. + */ + FIROAuthProvider *_googleOAuthProvider; + + /** @var _microsoftOAuthProvider + @brief OAuth provider instance for Microsoft. + */ + FIROAuthProvider *_microsoftOAuthProvider; } /** @fn initWithNibName:bundle: @@ -744,6 +754,8 @@ - (id)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundl _actionCodeContinueURL = [NSURL URLWithString:KCONTINUE_URL]; _authStateDidChangeListeners = [NSMutableArray array]; _IDTokenDidChangeListeners = [NSMutableArray array]; + _googleOAuthProvider = [FIROAuthProvider providerWithProviderID:FIRGoogleAuthProviderID]; + _microsoftOAuthProvider = [FIROAuthProvider providerWithProviderID:@"microsoft.com"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(authStateChangedForAuth:) name:FIRAuthStateDidChangeNotification @@ -1119,8 +1131,9 @@ - (void)signInWithProvider:(nonnull id)provider callback:(void(^)( [self logFailedTest:@"The test needs a valid credential to continue."]; return; } - [[AppManager auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { + [[AppManager auth] signInAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { if (error) { [self logFailure:@"sign-in with provider failed" error:error]; [self logFailedTest:@"Sign-in should succeed"]; @@ -1271,9 +1284,9 @@ - (void)automatedEmailSignUp { [auth signOut:NULL]; FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:kFakeEmail password:kFakePassword]; - [auth signInWithCredential:credential - completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { + [auth signInAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { if (error) { [self logFailure:@"sign-in with Email/Password failed" error:error]; [self logFailedTest:@"sign-in with Email/Password should succeed."]; @@ -1369,8 +1382,10 @@ - (void)automatedAccountLinking { callback:^(FIRAuthCredential *credential, NSError *error) { if (credential) { - [result.user linkWithCredential:credential completion:^(FIRUser *user, + [result.user linkAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *result, NSError *error) { + FIRUser *user = result.user; if (error) { [self logFailure:@"link auth provider failed" error:error]; [self logFailedTest:@"Account needs to be linked to complete the test."]; @@ -1561,9 +1576,9 @@ - (void)updateEmailPasswordWithCompletion:(void(^)(void))completion { [auth signOut:NULL]; FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:kFakeEmail password:kFakePassword]; - [auth signInWithCredential:credential - completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { + [auth signInAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { if (error) { [self logFailure:@"sign-in with Email/Password failed" error:error]; [self logFailedTest:@"sign-in with Email/Password should succeed."]; @@ -1782,7 +1797,7 @@ - (void)signInGoogleAndRetrieveData { } - (void)signInGoogleProvider { - FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:FIRGoogleAuthProviderID]; + FIROAuthProvider *provider = _googleOAuthProvider; provider.customParameters = @{ @"prompt" : @"consent", }; @@ -1816,7 +1831,7 @@ - (void)signInGoogleProvider { @brief Invoked when "Sign in with Google (headful-lite)" row is pressed. */ - (void)signInGoogleHeadfulLite { - FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:FIRGoogleAuthProviderID]; + FIROAuthProvider *provider = _googleOAuthProvider; provider.customParameters = @{ @"prompt" : @"consent", }; @@ -1850,7 +1865,7 @@ - (void)signInGoogleHeadfulLite { @brief Invoked when "Sign in with Microsoft (headful-lite)" row is pressed. */ - (void)signInMicrosoftHeadfulLite { - FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:FIRMicrosoftAuthProviderID]; + FIROAuthProvider *provider = _microsoftOAuthProvider; provider.customParameters = @{ @"prompt" : @"consent", @"login_hint" : @"tu8731@gmail.com", @@ -1991,9 +2006,9 @@ - (void)signInEmailPassword { [FIREmailAuthProvider credentialWithEmail:email password:password]; [self showSpinner:^{ - [[AppManager auth] signInWithCredential:credential - completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { + [[AppManager auth] signInAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { [self hideSpinner:^{ if (error) { [self logFailure:@"sign-in with Email/Password failed" error:error]; @@ -2021,10 +2036,10 @@ - (void)signInEmailPasswordAuthDataResult { return; } [self showSpinner:^{ - [[AppManager auth] signInAndRetrieveDataWithEmail:email - password:password - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[AppManager auth] signInWithEmail:email + password:password + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { [self hideSpinner:^{ if (error) { [self logFailure:@"sign-in with Email/Password failed" error:error]; @@ -2258,8 +2273,9 @@ - (void)reauthenticateEmailPassword { } [self showEmailPasswordDialogWithCompletion:^(FIRAuthCredential *credential) { [self showSpinner:^{ - [[self user] reauthenticateWithCredential:credential - completion:^(NSError *_Nullable error) { + [[self user] reauthenticateAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { if (error) { [self logFailure:@"reauthicate with email/password failed" error:error]; } else { @@ -2309,13 +2325,14 @@ - (void)reauthenticate:(id)authProvider retrieveData:(BOOL)retriev } [self showTypicalUIForUserUpdateResultsWithTitle:@"Reauthenticate" error:error]; }; - FIRUserProfileChangeCallback callback = ^(NSError *_Nullable error) { + FIRAuthDataResultCallback callback = ^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { completion(nil, error); }; if (retrieveData) { [user reauthenticateAndRetrieveDataWithCredential:credential completion:completion]; } else { - [user reauthenticateWithCredential:credential completion:callback]; + [user reauthenticateAndRetrieveDataWithCredential:credential completion:callback]; } } }]; @@ -2355,14 +2372,14 @@ - (void)signinWithProvider:(id)authProvider retrieveData:(BOOL)ret } [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error]; }; - FIRAuthResultCallback callback = ^(FIRUser *_Nullable user, - NSError *_Nullable error) { + FIRAuthDataResultCallback callback = ^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { completion(nil, error); }; if (retrieveData) { [auth signInAndRetrieveDataWithCredential:credential completion:completion]; } else { - [auth signInWithCredential:credential completion:callback]; + [auth signInAndRetrieveDataWithCredential:credential completion:callback]; } } }]; @@ -2539,14 +2556,14 @@ - (void)linkWithAuthProvider:(id)authProvider retrieveData:(BOOL)r [self showTypicalUIForUserUpdateResultsWithTitle:@"Link Account" error:error]; } }; - FIRAuthResultCallback callback = ^(FIRUser *_Nullable user, - NSError *_Nullable error) { + FIRAuthDataResultCallback callback = ^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { completion(nil, error); }; if (retrieveData) { [user linkAndRetrieveDataWithCredential:credential completion:completion]; } else { - [user linkWithCredential:credential completion:callback]; + [user linkAndRetrieveDataWithCredential:credential completion:callback]; } } }]; @@ -2590,8 +2607,8 @@ - (void)linkWithFacebookAndRetrieveData { - (void)linkWithEmailPassword { [self showEmailPasswordDialogWithCompletion:^(FIRAuthCredential *credential) { [self showSpinner:^{ - [[self user] linkWithCredential:credential - completion:^(FIRUser *user, NSError *_Nullable error) { + [[self user] linkAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *result, NSError *error) { if (error) { [self logFailure:@"link Email/Password failed" error:error]; } else { @@ -3096,10 +3113,10 @@ - (void)createUserAuthDataResult { } [self showSpinner:^{ - [[AppManager auth] createUserAndRetrieveDataWithEmail:email - password:password - completion:^(FIRAuthDataResult *_Nullable result, - NSError *_Nullable error) { + [[AppManager auth] createUserWithEmail:email + password:password + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { if (error) { [self logFailure:@"create user failed" error:error]; } else { @@ -3366,9 +3383,9 @@ - (void)linkPhoneNumber:(NSString *_Nullable)phoneNumber FIRPhoneAuthCredential *credential = [[AppManager phoneAuthProvider] credentialWithVerificationID:verificationID verificationCode:verificationCode]; - [[self user] linkWithCredential:credential - completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { + [[self user] linkAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { [self hideSpinner:^{ if (error) { if (error.code == FIRAuthErrorCodeCredentialAlreadyInUse) { @@ -3384,9 +3401,9 @@ - (void)linkPhoneNumber:(NSString *_Nullable)phoneNumber [self showSpinner:^{ FIRPhoneAuthCredential *credential = error.userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey]; - [[AppManager auth] signInWithCredential:credential - completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { + [[AppManager auth] signInAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { [self hideSpinner:^{ if (error) { [self logFailure:@"failed to verify phone number" error:error]; @@ -3455,7 +3472,7 @@ - (void)signInAnonymously { success. */ - (void)signInAnonymouslyAuthDataResult { - [[AppManager auth] signInAnonymouslyAndRetrieveDataWithCompletion: + [[AppManager auth] signInAnonymouslyWithCompletion: ^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { if (error) { [self logFailure:@"sign-in anonymously failed" error:error]; @@ -3480,9 +3497,9 @@ - (void)signInWithGitHub { FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:FIRGitHubAuthProviderID accessToken:accessToken]; if (credential) { - [[AppManager auth] signInWithCredential:credential - completion:^(FIRUser *_Nullable result, - NSError *_Nullable error) { + [[AppManager auth] signInAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { if (error) { [self logFailure:@"sign-in with provider failed" error:error]; } else { @@ -3648,9 +3665,9 @@ - (void)doSignInWithCustomToken:(NSString *_Nullable)userEnteredTokenText { } - (void)doSignInAndRetrieveDataWithCustomToken:(NSString *_Nullable)userEnteredTokenText { - [[AppManager auth] signInAndRetrieveDataWithCustomToken:userEnteredTokenText - completion:^(FIRAuthDataResult *_Nullable result, - NSError *_Nullable error) { + [[AppManager auth] signInWithCustomToken:userEnteredTokenText + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { if (error) { [self logFailure:@"sign-in with custom token failed" error:error]; [self showMessagePromptWithTitle:kSignInErrorAlertTitle diff --git a/Example/Auth/Sample/SettingsViewController.m b/Example/Auth/Sample/SettingsViewController.m index 07d4828fe11..b1e35f327e1 100644 --- a/Example/Auth/Sample/SettingsViewController.m +++ b/Example/Auth/Sample/SettingsViewController.m @@ -58,6 +58,11 @@ @"GoogleService-Info_multi" }; +/** @var kSharedKeychainAccessGroup + @brief The shared keychain access group for testing. + */ +static NSString *const kSharedKeychainAccessGroup = @"com.google.firebase.auth.keychainGroup1"; + /** @var gAPIEndpoints @brief List of API Hosts by request class name. */ @@ -155,6 +160,9 @@ - (void)setUpFirebaseAppOptions { } - (void)loadTableView { + NSString *appIdentifierPrefix = NSBundle.mainBundle.infoDictionary[@"AppIdentifierPrefix"]; + NSString *fullKeychainAccessGroup = [appIdentifierPrefix stringByAppendingString:kSharedKeychainAccessGroup]; + __weak typeof(self) weakSelf = self; _tableViewManager.contents = [StaticContentTableViewContent contentWithSections:@[ [StaticContentTableViewSection sectionWithTitle:@"Versions" cells:@[ @@ -191,6 +199,23 @@ - (void)loadTableView { [weakSelf toggleProjectForAppAtIndex:1]; }], ]], + [StaticContentTableViewSection sectionWithTitle:@"Keychain Access Groups" cells:@[ + [StaticContentTableViewCell cellWithTitle:@"Current Access Group" + value:[AppManager auth].userAccessGroup ? [AppManager auth].userAccessGroup : @"[none]" + ], + [StaticContentTableViewCell cellWithTitle:@"Default Group" + value:@"[none]" + action:^{ + [[AppManager auth] useUserAccessGroup:nil error:nil]; + [self loadTableView]; + }], + [StaticContentTableViewCell cellWithTitle:@"Shared Group" + value:fullKeychainAccessGroup + action:^{ + [[AppManager auth] useUserAccessGroup:fullKeychainAccessGroup error:nil]; + [self loadTableView]; + }], + ]], [StaticContentTableViewSection sectionWithTitle:@"Phone Auth" cells:@[ [StaticContentTableViewCell cellWithTitle:@"APNs Token" value:[self APNSTokenString] @@ -281,19 +306,14 @@ - (NSString *)projectIDForAppAtIndex:(int)index { return @"[none]"; } -/** @fn googleAppIDForAppAtIndex: - @brief Returns the Google App ID for the Firebase app at the given index. - @param index The index for the app in the app manager. - @return The Google App ID of the project. +/** @fn projectIDForAppAtIndex: + @brief Returns the Firebase project ID for the Firebase app at the given index. + @param index The index for the app in the app manager. + @return The ID of the project. */ -- (NSString *)googleAppIDForAppAtIndex:(int)index { - NSString *APIKey = [[AppManager sharedInstance] appAtIndex:index].options.APIKey; - for (FIROptions *options in gFirebaseAppOptions) { - if ([options.APIKey isEqualToString:APIKey]) { - return options.googleAppID; - } - } - return @"[none]"; +- (NSString *)keychainAccessGroupAtIndex:(int)index { + NSArray *array = @[@"123", @"456"]; + return array[index]; } /** @fn toggleProjectForAppAtIndex: diff --git a/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m b/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m deleted file mode 100644 index 92871c51e52..00000000000 --- a/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m +++ /dev/null @@ -1,755 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import -#import - -#import - -#import "FIRAuthAppDelegateProxy.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -/** @class FIRAuthEmptyAppDelegate - @brief A @c UIApplicationDelegate implementation that does nothing. - */ -@interface FIRAuthEmptyAppDelegate : NSObject -@end -@implementation FIRAuthEmptyAppDelegate -@end - -/** @class FIRAuthLegacyAppDelegate - @brief A @c UIApplicationDelegate implementation that implements - `application:didReceiveRemoteNotification:` and - `application:openURL:sourceApplication:annotation:`. - */ -@interface FIRAuthLegacyAppDelegate : NSObject - -/** @var notificationReceived - @brief The last notification received, if any. - */ -@property(nonatomic, copy, nullable) NSDictionary *notificationReceived; - -/** @var urlOpened - @brief The last URL opened, if any. - */ -@property(nonatomic, copy, nullable) NSURL *urlOpened; - -@end - -@implementation FIRAuthLegacyAppDelegate - -- (void)application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)userInfo { - self.notificationReceived = userInfo; -} - -- (BOOL)application:(UIApplication *)application - openURL:(NSURL *)url - sourceApplication:(nullable NSString *)sourceApplication - annotation:(id)annotation { - self.urlOpened = url; - return NO; -} - -@end - -/** @class FIRAuthModernAppDelegate - @brief A @c UIApplicationDelegate implementation that implements both - `application:didRegisterForRemoteNotificationsWithDeviceToken:`, - `application:didReceiveRemoteNotification:fetchCompletionHandler:`, and - `application:openURL:options:`. - */ -@interface FIRAuthModernAppDelegate : NSObject - -/** @var deviceTokenReceived - @brief The last device token received, if any. - */ -@property(nonatomic, copy, nullable) NSData *deviceTokenReceived; - -/** @var tokenErrorReceived - @brief The last token error received, if any. - */ -@property(nonatomic, copy, nullable) NSError *tokenErrorReceived; - -/** @var notificationReceived - @brief The last notification received, if any. - */ -@property(nonatomic, copy, nullable) NSDictionary *notificationReceived; - -/** @var urlOpened - @brief The last URL opened, if any. - */ -@property(nonatomic, copy, nullable) NSURL *urlOpened; - -@end - -@implementation FIRAuthModernAppDelegate - -- (void)application:(UIApplication *)application - didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - self.deviceTokenReceived = deviceToken; -} - -- (void)application:(UIApplication *)application - didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error { - self.tokenErrorReceived = error; -} - -- (void)application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)userInfo - fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - self.notificationReceived = userInfo; - completionHandler(UIBackgroundFetchResultNewData); -} - -- (BOOL)application:(UIApplication *)app - openURL:(NSURL *)url - options:(NSDictionary *)options { - self.urlOpened = url; - return NO; -} - -@end - -/** @class FIRAuthOtherLegacyAppDelegate - @brief A @c UIApplicationDelegate implementation that implements `application:handleOpenURL:`. - */ -@interface FIRAuthOtherLegacyAppDelegate : NSObject - -/** @var urlOpened - @brief The last URL opened, if any. - */ -@property(nonatomic, copy, nullable) NSURL *urlOpened; - -@end - -@implementation FIRAuthOtherLegacyAppDelegate - -- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url { - self.urlOpened = url; - return NO; -} - -@end - -/** @class FIRAuthAppDelegateProxyTests - @brief Unit tests for @c FIRAuthAppDelegateProxy . - */ -@interface FIRAuthAppDelegateProxyTests : XCTestCase -@end -@implementation FIRAuthAppDelegateProxyTests { - /** @var _mockApplication - @brief The mock UIApplication used for testing. - */ - id _mockApplication; - - /** @var _deviceToken - @brief The fake APNs device token for testing. - */ - NSData *_deviceToken; - - /** @var _error - @brief The fake error for testing. - */ - NSError *_error; - - /** @var _notification - @brief The fake notification for testing. - */ - NSDictionary* _notification; - - /** @var _url - @brief The fake URL for testing. - */ - NSURL *_url; - - /** @var _isIOS9orLater - @brief Whether the OS version is iOS 9 or later. - */ - BOOL _isIOS9orLater; -} - -- (void)setUp { - [super setUp]; - _mockApplication = OCMClassMock([UIApplication class]); - _deviceToken = [@"asdf" dataUsingEncoding:NSUTF8StringEncoding]; - _error = [NSError errorWithDomain:@"FakeError" code:12345 userInfo:nil]; - _notification = @{ @"zxcv" : @1234 }; - _url = [NSURL URLWithString:@"https://abc.def/ghi"]; - _isIOS9orLater = [[[UIDevice currentDevice] systemVersion] doubleValue] >= 9.0; -} - -- (void)tearDown { - OCMVerifyAll(_mockApplication); - [super tearDown]; -} - -/** @fn testSharedInstance - @brief Tests that the shared instance is the same one. - */ -- (void)testSharedInstance { - FIRAuthAppDelegateProxy *proxy1 = [FIRAuthAppDelegateProxy sharedInstance]; - FIRAuthAppDelegateProxy *proxy2 = [FIRAuthAppDelegateProxy sharedInstance]; - XCTAssertEqual(proxy1, proxy2); -} - -/** @fn testNilApplication - @brief Tests that initialization fails if the application is nil. - */ -- (void)testNilApplication { - XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:nil]); -} - -/** @fn testNilDelegate - @brief Tests that initialization fails if the application's delegate is nil. - */ -- (void)testNilDelegate { - OCMExpect([_mockApplication delegate]).andReturn(nil); - XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]); -} - -/** @fn testNonconformingDelegate - @brief Tests that initialization fails if the application's delegate does not conform to - @c UIApplicationDelegate protocol. - */ -- (void)testNonconformingDelegate { - OCMExpect([_mockApplication delegate]).andReturn(@"abc"); - XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]); -} - -/** @fn testDisabledByBundleEntry - @brief Tests that initialization fails if the proxy is disabled by a bundle entry. - */ -- (void)testDisabledByBundleEntry { - // Swizzle NSBundle's objectForInfoDictionaryKey to return @NO for the specific key. - Method method = class_getInstanceMethod([NSBundle class], @selector(objectForInfoDictionaryKey:)); - __block IMP originalImplementation; - IMP newImplmentation = imp_implementationWithBlock(^id(id object, NSString *key) { - if ([key isEqualToString:@"FirebaseAppDelegateProxyEnabled"]) { - return @NO; - } - typedef id (*Implementation)(id object, SEL cmd, NSString *key); - return ((Implementation)originalImplementation)(object, @selector(objectForInfoDictionaryKey:), - key); - }); - originalImplementation = method_setImplementation(method, newImplmentation); - - // Verify that initialization fails. - FIRAuthEmptyAppDelegate *delegate = [[FIRAuthEmptyAppDelegate alloc] init]; - OCMStub([_mockApplication delegate]).andReturn(delegate); - XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]); - - // Unswizzle. - imp_removeBlock(method_setImplementation(method, originalImplementation)); -} - -// Deprecated methods are call intentionally in tests to verify behaviors on older iOS systems. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - -/** @fn testEmptyDelegateOneHandler - @brief Tests that the proxy works against an empty @c UIApplicationDelegate for one handler. - */ -- (void)testEmptyDelegateOneHandler { - FIRAuthEmptyAppDelegate *delegate = [[FIRAuthEmptyAppDelegate alloc] init]; - OCMExpect([_mockApplication delegate]).andReturn(delegate); - __weak id weakProxy; - @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = - [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; - XCTAssertNotNil(proxy); - - // Verify certain methods are swizzled while others are not. - XCTAssertTrue([delegate respondsToSelector: - @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); - XCTAssertTrue([delegate respondsToSelector: - @selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); - XCTAssertTrue([delegate respondsToSelector: - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]); - XCTAssertFalse([delegate respondsToSelector: - @selector(application:didReceiveRemoteNotification:)]); - if (_isIOS9orLater) { - XCTAssertTrue([delegate respondsToSelector:@selector(application:openURL:options:)]); - XCTAssertFalse([delegate respondsToSelector: - @selector(application:openURL:sourceApplication:annotation:)]); - } else { - XCTAssertFalse([delegate respondsToSelector:@selector(application:openURL:options:)]); - XCTAssertTrue([delegate respondsToSelector: - @selector(application:openURL:sourceApplication:annotation:)]); - } - XCTAssertFalse([delegate respondsToSelector:@selector(application:handleOpenURL:)]); - - // Verify the handler is called after being added. - __weak id weakHandler; - @autoreleasepool { - id mockHandler = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler)); - [proxy addHandler:mockHandler]; - - // Verify `application:didRegisterForRemoteNotificationsWithDeviceToken:` is handled. - OCMExpect([mockHandler setAPNSToken:_deviceToken]); - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - OCMVerifyAll(mockHandler); - - // Verify `application:didFailToRegisterForRemoteNotificationsWithError:` is handled. - OCMExpect([mockHandler handleAPNSTokenError:_error]); - [delegate application:_mockApplication - didFailToRegisterForRemoteNotificationsWithError:_error]; - OCMVerifyAll(mockHandler); - - // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is handled. - OCMExpect([mockHandler canHandleNotification:_notification]).andReturn(YES); - __block BOOL fetchCompletionHandlerCalled = NO; - [delegate application:_mockApplication - didReceiveRemoteNotification:_notification - fetchCompletionHandler:^(UIBackgroundFetchResult result) { - XCTAssertEqual(result, UIBackgroundFetchResultNoData); - fetchCompletionHandlerCalled = YES; - }]; - OCMVerifyAll(mockHandler); - XCTAssertTrue(fetchCompletionHandlerCalled); - - // Verify one of the `application:openURL:...` methods is handled. - OCMExpect([mockHandler canHandleURL:_url]).andReturn(YES); - if (_isIOS9orLater) { - // Verify `application:openURL:options:` is handled. - XCTAssertTrue([delegate application:_mockApplication openURL:_url options:@{}]); - } else { - // Verify `application:openURL:sourceApplication:annotation:` is handled. - XCTAssertTrue([delegate application:_mockApplication - openURL:_url - sourceApplication:@"sourceApplication" - annotation:@"annotaton"]); - } - OCMVerifyAll(mockHandler); - - weakHandler = mockHandler; - XCTAssertNotNil(weakHandler); - } - // Verify the handler is not retained by the proxy. - XCTAssertNil(weakHandler); - - // Verify nothing bad happens after the handler is released. - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - [delegate application:_mockApplication - didFailToRegisterForRemoteNotificationsWithError:_error]; - [delegate application:_mockApplication - didReceiveRemoteNotification:_notification - fetchCompletionHandler:^(UIBackgroundFetchResult result) { - XCTFail(@"Should not call completion handler."); - }]; - if (_isIOS9orLater) { - XCTAssertFalse([delegate application:_mockApplication openURL:_url options:@{}]); - } else { - XCTAssertFalse([delegate application:_mockApplication - openURL:_url - sourceApplication:@"sourceApplication" - annotation:@"annotaton"]); - } - weakProxy = proxy; - XCTAssertNotNil(weakProxy); - } - // Verify the proxy does not retain itself. - XCTAssertNil(weakProxy); - // Verify nothing bad happens after the proxy is released. - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - [delegate application:_mockApplication - didReceiveRemoteNotification:_notification - fetchCompletionHandler:^(UIBackgroundFetchResult result) { - XCTFail(@"Should not call completion handler."); - }]; - if (_isIOS9orLater) { - XCTAssertNoThrow([delegate application:_mockApplication openURL:_url options:@{}]); - } else { - XCTAssertNoThrow([delegate application:_mockApplication - openURL:_url - sourceApplication:@"sourceApplication" - annotation:@"annotaton"]); - } -} - -/** @fn testLegacyDelegateTwoHandlers - @brief Tests that the proxy works against a legacy @c UIApplicationDelegate for two handlers. - */ -- (void)testLegacyDelegateTwoHandlers { - FIRAuthLegacyAppDelegate *delegate = [[FIRAuthLegacyAppDelegate alloc] init]; - OCMExpect([_mockApplication delegate]).andReturn(delegate); - __weak id weakProxy; - @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = - [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; - XCTAssertNotNil(proxy); - - // Verify certain methods are swizzled while others are not. - XCTAssertTrue([delegate respondsToSelector: - @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); - XCTAssertTrue([delegate respondsToSelector: - @selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); - XCTAssertFalse([delegate respondsToSelector: - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]); - XCTAssertTrue([delegate respondsToSelector: - @selector(application:didReceiveRemoteNotification:)]); - XCTAssertFalse([delegate respondsToSelector:@selector(application:openURL:options:)]); - XCTAssertTrue([delegate respondsToSelector: - @selector(application:openURL:sourceApplication:annotation:)]); - XCTAssertFalse([delegate respondsToSelector:@selector(application:handleOpenURL:)]); - - // Verify the handler is called after being added. - __weak id weakHandler1; - @autoreleasepool { - id mockHandler1 = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler)); - [proxy addHandler:mockHandler1]; - __weak id weakHandler2; - @autoreleasepool { - id mockHandler2 = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler)); - [proxy addHandler:mockHandler2]; - - // Verify `application:didRegisterForRemoteNotificationsWithDeviceToken:` is handled. - OCMExpect([mockHandler1 setAPNSToken:_deviceToken]); - OCMExpect([mockHandler2 setAPNSToken:_deviceToken]); - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - OCMVerifyAll(mockHandler1); - OCMVerifyAll(mockHandler2); - - // Verify `application:didFailToRegisterForRemoteNotificationsWithError:` is handled. - OCMExpect([mockHandler1 handleAPNSTokenError:_error]); - OCMExpect([mockHandler2 handleAPNSTokenError:_error]); - [delegate application:_mockApplication - didFailToRegisterForRemoteNotificationsWithError:_error]; - OCMVerifyAll(mockHandler1); - OCMVerifyAll(mockHandler2); - - // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is handled. - OCMExpect([mockHandler1 canHandleNotification:_notification]).andReturn(YES); - // handler2 shouldn't been invoked because it is already handled by handler1. - [delegate application:_mockApplication didReceiveRemoteNotification:_notification]; - OCMVerifyAll(mockHandler1); - OCMVerifyAll(mockHandler2); - XCTAssertNil(delegate.notificationReceived); - - // Verify `application:openURL:sourceApplication:annotation:` is handled. - OCMExpect([mockHandler1 canHandleURL:_url]).andReturn(YES); - XCTAssertTrue([delegate application:_mockApplication - openURL:_url - sourceApplication:@"sourceApplication" - annotation:@"annotaton"]); - OCMVerifyAll(mockHandler1); - OCMVerifyAll(mockHandler2); - XCTAssertNil(delegate.urlOpened); - - weakHandler2 = mockHandler2; - XCTAssertNotNil(weakHandler2); - } - // Verify the handler2 is not retained by the proxy. - XCTAssertNil(weakHandler2); - - // Verify `application:didRegisterForRemoteNotificationsWithDeviceToken:` is handled. - OCMExpect([mockHandler1 setAPNSToken:_deviceToken]); - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - OCMVerifyAll(mockHandler1); - - // Verify `application:didFailToRegisterForRemoteNotificationsWithError:` is handled. - OCMExpect([mockHandler1 handleAPNSTokenError:_error]); - [delegate application:_mockApplication - didFailToRegisterForRemoteNotificationsWithError:_error]; - OCMVerifyAll(mockHandler1); - - // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is NOT handled. - OCMExpect([mockHandler1 canHandleNotification:_notification]).andReturn(NO); - [delegate application:_mockApplication didReceiveRemoteNotification:_notification]; - OCMVerifyAll(mockHandler1); - XCTAssertEqualObjects(delegate.notificationReceived, _notification); - delegate.notificationReceived = nil; - - // Verify `application:openURL:sourceApplication:annotation:` is NOT handled. - OCMExpect([mockHandler1 canHandleURL:_url]).andReturn(NO); - XCTAssertFalse([delegate application:_mockApplication - openURL:_url - sourceApplication:@"sourceApplication" - annotation:@"annotation"]); - OCMVerifyAll(mockHandler1); - XCTAssertEqualObjects(delegate.urlOpened, _url); - delegate.urlOpened = nil; - - weakHandler1 = mockHandler1; - XCTAssertNotNil(weakHandler1); - } - // Verify the handler1 is not retained by the proxy. - XCTAssertNil(weakHandler1); - - // Verify the delegate still works after all handlers are released. - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - [delegate application:_mockApplication - didFailToRegisterForRemoteNotificationsWithError:_error]; - [delegate application:_mockApplication didReceiveRemoteNotification:_notification]; - XCTAssertEqualObjects(delegate.notificationReceived, _notification); - delegate.notificationReceived = nil; - XCTAssertFalse([delegate application:_mockApplication - openURL:_url - sourceApplication:@"sourceApplication" - annotation:@"annotation"]); - XCTAssertEqualObjects(delegate.urlOpened, _url); - delegate.urlOpened = nil; - - weakProxy = proxy; - XCTAssertNotNil(weakProxy); - } - // Verify the proxy does not retain itself. - XCTAssertNil(weakProxy); - - // Verify the delegate still works after the proxy is released. - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - [delegate application:_mockApplication didReceiveRemoteNotification:_notification]; - XCTAssertEqualObjects(delegate.notificationReceived, _notification); - delegate.notificationReceived = nil; - XCTAssertFalse([delegate application:_mockApplication - openURL:_url - sourceApplication:@"sourceApplication" - annotation:@"annotation"]); - XCTAssertEqualObjects(delegate.urlOpened, _url); - delegate.urlOpened = nil; -} - -/** @fn testModernDelegateWithOtherInstance - @brief Tests that the proxy works against a modern @c UIApplicationDelegate along with - another unaffected instance. - */ -- (void)testModernDelegateWithUnaffectedInstance { - FIRAuthModernAppDelegate *delegate = [[FIRAuthModernAppDelegate alloc] init]; - OCMExpect([_mockApplication delegate]).andReturn(delegate); - FIRAuthModernAppDelegate *unaffectedDelegate = [[FIRAuthModernAppDelegate alloc] init]; - __weak id weakProxy; - @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = - [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; - XCTAssertNotNil(proxy); - - // Verify certain methods are swizzled while others are not. - XCTAssertTrue([delegate respondsToSelector: - @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); - XCTAssertTrue([delegate respondsToSelector: - @selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); - XCTAssertTrue([delegate respondsToSelector: - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]); - XCTAssertFalse([delegate respondsToSelector: - @selector(application:didReceiveRemoteNotification:)]); - XCTAssertTrue([delegate respondsToSelector:@selector(application:openURL:options:)]); - if (_isIOS9orLater) { - XCTAssertFalse([delegate respondsToSelector: - @selector(application:openURL:sourceApplication:annotation:)]); - } else { - XCTAssertTrue([delegate respondsToSelector: - @selector(application:openURL:sourceApplication:annotation:)]); - } - XCTAssertFalse([delegate respondsToSelector:@selector(application:handleOpenURL:)]); - - // Verify the handler is called after being added. - __weak id weakHandler; - @autoreleasepool { - id mockHandler = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler)); - [proxy addHandler:mockHandler]; - - // Verify `application:didRegisterForRemoteNotificationsWithDeviceToken:` is handled. - OCMExpect([mockHandler setAPNSToken:_deviceToken]); - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - OCMVerifyAll(mockHandler); - XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken); - delegate.deviceTokenReceived = nil; - - // Verify `application:didFailToRegisterForRemoteNotificationsWithError:` is handled. - OCMExpect([mockHandler handleAPNSTokenError:_error]); - [delegate application:_mockApplication - didFailToRegisterForRemoteNotificationsWithError:_error]; - OCMVerifyAll(mockHandler); - XCTAssertEqualObjects(delegate.tokenErrorReceived, _error); - delegate.tokenErrorReceived = nil; - - // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is handled. - OCMExpect([mockHandler canHandleNotification:_notification]).andReturn(YES); - __block BOOL fetchCompletionHandlerCalled = NO; - [delegate application:_mockApplication - didReceiveRemoteNotification:_notification - fetchCompletionHandler:^(UIBackgroundFetchResult result) { - XCTAssertEqual(result, UIBackgroundFetchResultNoData); - fetchCompletionHandlerCalled = YES; - }]; - OCMVerifyAll(mockHandler); - XCTAssertTrue(fetchCompletionHandlerCalled); - XCTAssertNil(delegate.notificationReceived); - - // Verify one of the `application:openURL:...` methods is handled. - OCMExpect([mockHandler canHandleURL:_url]).andReturn(YES); - if (_isIOS9orLater) { - // Verify `application:openURL:options:` is handled. - XCTAssertTrue([delegate application:_mockApplication openURL:_url options:@{}]); - } else { - // Verify `application:openURL:sourceApplication:annotation:` is handled. - XCTAssertTrue([delegate application:_mockApplication - openURL:_url - sourceApplication:@"sourceApplication" - annotation:@"annotaton"]); - } - OCMVerifyAll(mockHandler); - XCTAssertNil(delegate.urlOpened); - - // Verify unaffected delegate instance. - [unaffectedDelegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - XCTAssertEqualObjects(unaffectedDelegate.deviceTokenReceived, _deviceToken); - unaffectedDelegate.deviceTokenReceived = nil; - [unaffectedDelegate application:_mockApplication - didFailToRegisterForRemoteNotificationsWithError:_error]; - XCTAssertEqualObjects(unaffectedDelegate.tokenErrorReceived, _error); - unaffectedDelegate.tokenErrorReceived = nil; - fetchCompletionHandlerCalled = NO; - [unaffectedDelegate application:_mockApplication - didReceiveRemoteNotification:_notification - fetchCompletionHandler:^(UIBackgroundFetchResult result) { - XCTAssertEqual(result, UIBackgroundFetchResultNewData); - fetchCompletionHandlerCalled = YES; - }]; - XCTAssertTrue(fetchCompletionHandlerCalled); - XCTAssertEqualObjects(unaffectedDelegate.notificationReceived, _notification); - unaffectedDelegate.notificationReceived = nil; - XCTAssertFalse([unaffectedDelegate application:_mockApplication openURL:_url options:@{}]); - XCTAssertEqualObjects(unaffectedDelegate.urlOpened, _url); - unaffectedDelegate.urlOpened = nil; - - weakHandler = mockHandler; - XCTAssertNotNil(weakHandler); - } - // Verify the handler is not retained by the proxy. - XCTAssertNil(weakHandler); - - // Verify the delegate still works after the handler is released. - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken); - delegate.deviceTokenReceived = nil; - [delegate application:_mockApplication - didFailToRegisterForRemoteNotificationsWithError:_error]; - XCTAssertEqualObjects(delegate.tokenErrorReceived, _error); - delegate.tokenErrorReceived = nil; - __block BOOL fetchCompletionHandlerCalled = NO; - [delegate application:_mockApplication - didReceiveRemoteNotification:_notification - fetchCompletionHandler:^(UIBackgroundFetchResult result) { - XCTAssertEqual(result, UIBackgroundFetchResultNewData); - fetchCompletionHandlerCalled = YES; - }]; - XCTAssertEqualObjects(delegate.notificationReceived, _notification); - delegate.notificationReceived = nil; - XCTAssertTrue(fetchCompletionHandlerCalled); - XCTAssertFalse([delegate application:_mockApplication openURL:_url options:@{}]); - XCTAssertEqualObjects(delegate.urlOpened, _url); - delegate.urlOpened = nil; - - weakProxy = proxy; - XCTAssertNotNil(weakProxy); - } - // Verify the proxy does not retain itself. - XCTAssertNil(weakProxy); - - // Verify the delegate still works after the proxy is released. - [delegate application:_mockApplication - didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; - XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken); - delegate.deviceTokenReceived = nil; - [delegate application:_mockApplication - didFailToRegisterForRemoteNotificationsWithError:_error]; - XCTAssertEqualObjects(delegate.tokenErrorReceived, _error); - delegate.tokenErrorReceived = nil; - __block BOOL fetchCompletionHandlerCalled = NO; - [delegate application:_mockApplication - didReceiveRemoteNotification:_notification - fetchCompletionHandler:^(UIBackgroundFetchResult result) { - XCTAssertEqual(result, UIBackgroundFetchResultNewData); - fetchCompletionHandlerCalled = YES; - }]; - XCTAssertEqualObjects(delegate.notificationReceived, _notification); - delegate.notificationReceived = nil; - XCTAssertTrue(fetchCompletionHandlerCalled); - XCTAssertFalse([delegate application:_mockApplication openURL:_url options:@{}]); - XCTAssertEqualObjects(delegate.urlOpened, _url); - delegate.urlOpened = nil; -} - -/** @fn testOtherLegacyDelegateHandleOpenURL - @brief Tests that the proxy works against another legacy @c UIApplicationDelegate for - `application:handleOpenURL:`. - */ -- (void)testOtherLegacyDelegateHandleOpenURL { - FIRAuthOtherLegacyAppDelegate *delegate = [[FIRAuthOtherLegacyAppDelegate alloc] init]; - OCMExpect([_mockApplication delegate]).andReturn(delegate); - __weak id weakProxy; - @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = - [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; - XCTAssertNotNil(proxy); - - // Verify certain methods are swizzled while others are not. - XCTAssertFalse([delegate respondsToSelector:@selector(application:openURL:options:)]); - XCTAssertFalse([delegate respondsToSelector: - @selector(application:openURL:sourceApplication:annotation:)]); - XCTAssertTrue([delegate respondsToSelector:@selector(application:handleOpenURL:)]); - - // Verify the handler is called after being added. - __weak id weakHandler; - @autoreleasepool { - id mockHandler = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler)); - [proxy addHandler:mockHandler]; - - // Verify `application:handleOpenURL:` is handled. - OCMExpect([mockHandler canHandleURL:_url]).andReturn(YES); - XCTAssertTrue([delegate application:_mockApplication handleOpenURL:_url]); - OCMVerifyAll(mockHandler); - - weakHandler = mockHandler; - XCTAssertNotNil(weakHandler); - } - // Verify the handler is not retained by the proxy. - XCTAssertNil(weakHandler); - - // Verify nothing bad happens after the handler is released. - XCTAssertFalse([delegate application:_mockApplication handleOpenURL:_url]); - XCTAssertEqualObjects(delegate.urlOpened, _url); - delegate.urlOpened = nil; - - weakProxy = proxy; - XCTAssertNotNil(weakProxy); - } - // Verify the proxy does not retain itself. - XCTAssertNil(weakProxy); - // Verify nothing bad happens after the proxy is released. - XCTAssertFalse([delegate application:_mockApplication handleOpenURL:_url]); - XCTAssertEqualObjects(delegate.urlOpened, _url); - delegate.urlOpened = nil; -} - -#pragma clang diagnostic pop // ignored "-Wdeprecated-declarations" - -@end - -NS_ASSUME_NONNULL_END diff --git a/Example/Auth/Tests/FIRAuthTests.m b/Example/Auth/Tests/FIRAuthTests.m index 557447a8a58..7273b85cda5 100644 --- a/Example/Auth/Tests/FIRAuthTests.m +++ b/Example/Auth/Tests/FIRAuthTests.m @@ -28,6 +28,8 @@ #import #import +#import + #import "FIRAdditionalUserInfo.h" #import "FIRAuth_Internal.h" #import "FIRAuthOperationType.h" @@ -70,10 +72,14 @@ #import "FIRActionCodeSettings.h" #if TARGET_OS_IOS +#import "FIRAuthAPNSToken.h" +#import "FIRAuthAPNSTokenManager.h" +#import "FIRAuthNotificationManager.h" #import "FIRAuthUIDelegate.h" +#import "FIRAuthURLPresenter.h" #import "FIRPhoneAuthCredential.h" #import "FIRPhoneAuthProvider.h" -#endif +#endif // TARGET_OS_IOS /** @var kAPIKey @brief The fake API key. @@ -236,6 +242,40 @@ */ static const NSTimeInterval kWaitInterval = .5; +#if TARGET_OS_IOS +/** @class FIRAuthAppDelegate + @brief Application delegate implementation to test the app delegate proxying + */ +@interface FIRAuthAppDelegate : NSObject +@end + +@implementation FIRAuthAppDelegate +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { +} + +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + return NO; +} + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(nullable NSString *)sourceApplication + annotation:(id)annotation { + return NO; +} + +@end + +#endif // TARGET_OS_IOS + +@interface GULAppDelegateSwizzler (FIRMessagingRemoteNotificationsProxyTest) ++ (void)resetProxyOriginalDelegateOnceToken; +@end + /** Category for FIRAuth to expose FIRComponentRegistrant conformance. */ @interface FIRAuth () @end @@ -244,7 +284,20 @@ @interface FIRAuth () @brief Tests for @c FIRAuth. */ @interface FIRAuthTests : XCTestCase +#if TARGET_OS_IOS +/// A partial mock of `[FIRAuth auth].tokenManager` +@property(nonatomic, strong) id mockTokenManager; +/// A partial mock of `[FIRAuth auth].notificationManager` +@property(nonatomic, strong) id mockNotificationManager; +/// A partial mock of `[FIRAuth auth].authURLPresenter` +@property(nonatomic, strong) id mockAuthURLPresenter; +/// A partial mock of `[UIApplication sharedApplication]` +@property(nonatomic, strong) id mockApplication; +/// An application delegate instance returned by `self.mockApplication.delegate` +@property(nonatomic, strong) FIRAuthAppDelegate *fakeApplicationDelegate; +#endif // TARGET_OS_IOS @end + @implementation FIRAuthTests { /** @var _mockBackend @@ -277,6 +330,16 @@ + (NSDictionary *)googleProfile { - (void)setUp { [super setUp]; + +#if TARGET_OS_IOS + // Make sure the `self.fakeApplicationDelegate` will be swizzled on FIRAuth init. + [GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken]; + + self.fakeApplicationDelegate = [[FIRAuthAppDelegate alloc] init]; + self.mockApplication = OCMPartialMock([UIApplication sharedApplication]); + OCMStub([self.mockApplication delegate]).andReturn(self.fakeApplicationDelegate); +#endif // TARGET_OS_IOS + _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation)); [FIRAuthBackend setBackendImplementation:_mockBackend]; [FIRApp resetAppForAuthUnitTests]; @@ -292,11 +355,32 @@ - (void)setUp { XCTAssertEqualObjects(FIRAuthGlobalWorkQueue(), queue); _FIRAuthDispatcherCallback = task; }]; + +#if TARGET_OS_IOS + // Wait until FIRAuth initialization completes + [self waitForAuthGlobalWorkQueueDrain]; + self.mockTokenManager = OCMPartialMock([FIRAuth auth].tokenManager); + self.mockNotificationManager = OCMPartialMock([FIRAuth auth].notificationManager); + self.mockAuthURLPresenter = OCMPartialMock([FIRAuth auth].authURLPresenter); +#endif // TARGET_OS_IOS } - (void)tearDown { [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil]; [[FIRAuthDispatcher sharedInstance] setDispatchAfterImplementation:nil]; + +#if TARGET_OS_IOS + [self.mockAuthURLPresenter stopMocking]; + self.mockAuthURLPresenter = nil; + [self.mockNotificationManager stopMocking]; + self.mockNotificationManager = nil; + [self.mockTokenManager stopMocking]; + self.mockTokenManager = nil; + [self.mockApplication stopMocking]; + self.mockApplication = nil; + self.fakeApplicationDelegate = nil; +#endif // TARGET_OS_IOS + [super tearDown]; } @@ -367,42 +451,6 @@ - (void)testFetchSignInMethodsForEmailSuccess { OCMVerifyAll(_mockBackend); } -/** @fn testFetchProvidersForEmailSuccessDeprecatedProviderID - @brief Tests the flow of a successful @c fetchProvidersForEmail:completion: call using the - deprecated FIREmailPasswordAuthProviderID. - */ -- (void)testFetchProvidersForEmailSuccessDeprecatedProviderID { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSArray *allProviders = - @[ FIRGoogleAuthProviderID, FIREmailPasswordAuthProviderID ]; -#pragma clang diagnostic pop - OCMExpect([_mockBackend createAuthURI:[OCMArg any] - callback:[OCMArg any]]) - .andCallBlock2(^(FIRCreateAuthURIRequest *_Nullable request, - FIRCreateAuthURIResponseCallback callback) { - XCTAssertEqualObjects(request.identifier, kEmail); - XCTAssertNotNil(request.endpoint); - XCTAssertEqualObjects(request.APIKey, kAPIKey); - dispatch_async(FIRAuthGlobalWorkQueue(), ^() { - id mockCreateAuthURIResponse = OCMClassMock([FIRCreateAuthURIResponse class]); - OCMStub([mockCreateAuthURIResponse allProviders]).andReturn(allProviders); - callback(mockCreateAuthURIResponse, nil); - }); - }); - XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - [[FIRAuth auth] fetchProvidersForEmail:kEmail - completion:^(NSArray *_Nullable providers, - NSError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - XCTAssertEqualObjects(providers, allProviders); - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; - OCMVerifyAll(_mockBackend); -} - /** @fn testFetchProvidersForEmailFailure @brief Tests the flow of a failed @c fetchProvidersForEmail:completion: call. */ @@ -468,9 +516,9 @@ - (void)testPhoneAuthSuccess { [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:credential - completion:^(FIRAuthDataResult *_Nullable authDataResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable authDataResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUser:authDataResult.user]; XCTAssertTrue(authDataResult.additionalUserInfo.isNewUser); @@ -494,10 +542,10 @@ - (void)testPhoneAuthMissingVerificationCode { [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:@""]; - [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user, + [[FIRAuth auth] signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); - XCTAssertNil(user); + XCTAssertNil(result); XCTAssertEqual(error.code, FIRAuthErrorCodeMissingVerificationCode); [expectation fulfill]; }]; @@ -516,10 +564,10 @@ - (void)testPhoneAuthMissingVerificationID { [[FIRPhoneAuthProvider provider] credentialWithVerificationID:@"" verificationCode:kVerificationCode]; - [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user, + [[FIRAuth auth] signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); - XCTAssertNil(user); + XCTAssertNil(result); XCTAssertEqual(error.code, FIRAuthErrorCodeMissingVerificationID); [expectation fulfill]; }]; @@ -700,10 +748,9 @@ - (void)testSignInAndRetrieveDataWithEmailPasswordSuccess { [self expectGetAccountInfo]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] signInAndRetrieveDataWithEmail:kEmail - password:kFakePassword - completion:^(FIRAuthDataResult *_Nullable result, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithEmail:kEmail + password:kFakePassword + completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUser:result.user]; XCTAssertFalse(result.additionalUserInfo.isNewUser); @@ -724,10 +771,9 @@ - (void)testSignInAndRetrieveDataWithEmailPasswordFailure { .andDispatchError2([FIRAuthErrorUtils wrongPasswordErrorWithMessage:nil]); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] signInAndRetrieveDataWithEmail:kEmail - password:kFakePassword - completion:^(FIRAuthDataResult *_Nullable result, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithEmail:kEmail + password:kFakePassword + completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(result); XCTAssertEqual(error.code, FIRAuthErrorCodeWrongPassword); @@ -976,9 +1022,9 @@ - (void)testSignInWithEmailLinkCredentialSuccess { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail link:kFakeEmailSignInlink]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:emailCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:emailCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNotNil(authResult.user); XCTAssertEqualObjects(authResult.user.refreshToken, kRefreshToken); @@ -1004,10 +1050,10 @@ - (void)testSignInWithEmailLinkCredentialFailure { FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail link:kFakeEmailSignInlink]; [[FIRAuth auth] signInWithCredential:emailCredential - completion:^(FIRUser *_Nullable user, + completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); - XCTAssertNil(user); + XCTAssertNil(result); XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]); [expectation fulfill]; @@ -1041,49 +1087,10 @@ - (void)testSignInWithEmailCredentialSuccess { FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [[FIRAuth auth] signInWithCredential:emailCredential - completion:^(FIRUser *_Nullable user, + completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); - [self assertUser:user]; - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; - [self assertUser:[FIRAuth auth].currentUser]; - OCMVerifyAll(_mockBackend); -} - -/** @fn testSignInWithEmailCredentialSuccess - @brief Tests the flow of a successfully @c signInWithCredential:completion: call with an - email-password credential using the deprecated FIREmailPasswordAuthProvider. - */ -- (void)testSignInWithEmailCredentialSuccessWithDepricatedProvider { - OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]]) - .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request, - FIRVerifyPasswordResponseCallback callback) { - XCTAssertEqualObjects(request.APIKey, kAPIKey); - XCTAssertEqualObjects(request.email, kEmail); - XCTAssertEqualObjects(request.password, kFakePassword); - XCTAssertTrue(request.returnSecureToken); - dispatch_async(FIRAuthGlobalWorkQueue(), ^() { - id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]); - [self stubTokensWithMockResponse:mockVeriyPasswordResponse]; - callback(mockVeriyPasswordResponse, nil); - }); - }); - [self expectGetAccountInfo]; - XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - [[FIRAuth auth] signOut:NULL]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - FIRAuthCredential *emailCredential = - [FIREmailPasswordAuthProvider credentialWithEmail:kEmail password:kFakePassword]; -#pragma clang diagnostic pop - [[FIRAuth auth] signInWithCredential:emailCredential - completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - [self assertUser:user]; + [self assertUser:result.user]; XCTAssertNil(error); [expectation fulfill]; }]; @@ -1104,10 +1111,10 @@ - (void)testSignInWithEmailCredentialFailure { FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [[FIRAuth auth] signInWithCredential:emailCredential - completion:^(FIRUser *_Nullable user, + completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); - XCTAssertNil(user); + XCTAssertNil(result); XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]); [expectation fulfill]; @@ -1130,7 +1137,8 @@ - (void)testSignInWithEmailCredentialEmptyPassword { FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:emptyString]; [[FIRAuth auth] signInWithCredential:emailCredential - completion:^(FIRUser *_Nullable user, NSError *_Nullable error) { + completion:^(FIRAuthDataResult * _Nullable result, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeWrongPassword); [expectation fulfill]; @@ -1250,7 +1258,7 @@ - (void)testSignInWithGoogleAccountExistsError { FIRAuthCredential *googleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [[FIRAuth auth] signInWithCredential:googleCredential - completion:^(FIRUser *_Nullable user, + completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeAccountExistsWithDifferentCredential); @@ -1290,10 +1298,10 @@ - (void)testSignInWithGoogleCredentialSuccess { FIRAuthCredential *googleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [[FIRAuth auth] signInWithCredential:googleCredential - completion:^(FIRUser *_Nullable user, + completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); - [self assertUserGoogle:user]; + [self assertUserGoogle:result.user]; XCTAssertNil(error); [expectation fulfill]; }]; @@ -1346,10 +1354,11 @@ - (void)testSignInWithOAuthCredentialSuccess { FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential; XCTAssertEqualObjects(OAuthCredential.OAuthResponseURLString, kOAuthRequestURI); XCTAssertEqualObjects(OAuthCredential.sessionID, kOAuthSessionID); - [[FIRAuth auth] signInWithCredential:OAuthCredential completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:OAuthCredential + completion:^(FIRAuthDataResult * _Nullable result, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); - [self assertUserGoogle:user]; + [self assertUserGoogle:result.user]; XCTAssertNil(error); [expectation fulfill]; }]; @@ -1390,9 +1399,9 @@ - (void)testSignInAndRetrieveDataWithCredentialSuccess { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *googleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:googleCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:googleCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserGoogle:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1418,10 +1427,10 @@ - (void)testSignInWithGoogleCredentialFailure { FIRAuthCredential *googleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [[FIRAuth auth] signInWithCredential:googleCredential - completion:^(FIRUser *_Nullable user, + completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); - XCTAssertNil(user); + XCTAssertNil(result.user); XCTAssertEqual(error.code, FIRAuthErrorCodeEmailAlreadyInUse); XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]); [expectation fulfill]; @@ -1504,7 +1513,7 @@ - (void)testSignInAnonymouslyAndRetrieveDataSuccess { [self expectGetAccountInfoAnonymous]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] signInAnonymouslyAndRetrieveDataWithCompletion: + [[FIRAuth auth] signInAnonymouslyWithCompletion: ^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserAnonymous:result.user]; @@ -1524,7 +1533,7 @@ - (void)testSignInAnonymouslyAndRetrieveDataFailure { .andDispatchError2([FIRAuthErrorUtils operationNotAllowedErrorWithMessage:nil]); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] signInAnonymouslyAndRetrieveDataWithCompletion: + [[FIRAuth auth] signInAnonymouslyWithCompletion: ^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(result); @@ -1609,9 +1618,9 @@ - (void)testSignInAndRetrieveDataWithCustomTokenSuccess { [self expectGetAccountInfo]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] signInAndRetrieveDataWithCustomToken:kCustomToken - completion:^(FIRAuthDataResult *_Nullable result, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCustomToken:kCustomToken + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUser:result.user]; XCTAssertFalse(result.additionalUserInfo.isNewUser); @@ -1631,9 +1640,9 @@ - (void)testSignInAndRetrieveDataWithCustomTokenFailure { .andDispatchError2([FIRAuthErrorUtils invalidCustomTokenErrorWithMessage:nil]); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] signInAndRetrieveDataWithCustomToken:kCustomToken - completion:^(FIRAuthDataResult *_Nullable result, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCustomToken:kCustomToken + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(result); XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidCustomToken); @@ -1724,10 +1733,10 @@ - (void)testCreateUserAndRetrieveDataWithEmailPasswordSuccess { [self expectGetAccountInfo]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] createUserAndRetrieveDataWithEmail:kEmail - password:kFakePassword - completion:^(FIRAuthDataResult *_Nullable result, - NSError *_Nullable error) { + [[FIRAuth auth] createUserWithEmail:kEmail + password:kFakePassword + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUser:result.user]; XCTAssertTrue(result.additionalUserInfo.isNewUser); @@ -1749,10 +1758,10 @@ - (void)testCreateUserAndRetrieveDataWithEmailPasswordFailure { .andDispatchError2([FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:reason]); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] createUserAndRetrieveDataWithEmail:kEmail - password:kFakePassword - completion:^(FIRAuthDataResult *_Nullable result, - NSError *_Nullable error) { + [[FIRAuth auth] createUserWithEmail:kEmail + password:kFakePassword + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(result); XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword); @@ -2294,6 +2303,90 @@ - (void)testAutoRefreshAppForegroundedNotification { } #endif +#if TARGET_OS_IOS +#pragma mark - Application Delegate tests +- (void)testAppDidRegisterForRemoteNotifications_APNSTokenUpdated { + NSData *apnsToken = [NSData data]; + + OCMExpect([self.mockTokenManager setToken:[OCMArg checkWithBlock:^BOOL(FIRAuthAPNSToken *token) { + XCTAssertEqual(token.data, apnsToken); + XCTAssertEqual(token.type, FIRAuthAPNSTokenTypeUnknown); + return YES; + }]]); + + [self.fakeApplicationDelegate application:self.mockApplication +didRegisterForRemoteNotificationsWithDeviceToken:apnsToken]; + + [self.mockTokenManager verify]; +} + +- (void)testAppDidFailToRegisterForRemoteNotifications_TokenManagerCancels { + NSError *error = [NSError errorWithDomain:@"FIRAuthTests" code:-1 userInfo:nil]; + + OCMExpect([self.mockTokenManager cancelWithError:error]); + + [self.fakeApplicationDelegate application:self.mockApplication +didFailToRegisterForRemoteNotificationsWithError:error]; + + [self.mockTokenManager verify]; +} + +- (void)testAppDidReceiveRemoteNotification_NotificationManagerHandleCanNotification { + NSDictionary *notification = @{@"test" : @""}; + + OCMExpect([self.mockNotificationManager canHandleNotification:notification]); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self.fakeApplicationDelegate application:self.mockApplication + didReceiveRemoteNotification:notification]; +#pragma clang diagnostic pop + + [self.mockNotificationManager verify]; +} + +- (void)testAppDidReceiveRemoteNotificationWithCompletion_NotificationManagerHandleCanNotification { + NSDictionary *notification = @{@"test" : @""}; + + OCMExpect([self.mockNotificationManager canHandleNotification:notification]); + + [self.fakeApplicationDelegate application:self.mockApplication + didReceiveRemoteNotification:notification + fetchCompletionHandler:^(UIBackgroundFetchResult result) {}]; + + [self.mockNotificationManager verify]; +} + +- (void)testAppOpenURL_AuthPresenterCanHandleURL { + NSURL *url = [NSURL URLWithString:@"https://localhost"]; + + [OCMExpect([self.mockAuthURLPresenter canHandleURL:url]) andReturnValue:@(YES)]; + + XCTAssertTrue([self.fakeApplicationDelegate application:self.mockApplication + openURL:url + options:@{}]); + + [self.mockAuthURLPresenter verify]; +} + +- (void)testAppOpenURLWithSourceApplication_AuthPresenterCanHandleURL { + NSURL *url = [NSURL URLWithString:@"https://localhost"]; + + [OCMExpect([self.mockAuthURLPresenter canHandleURL:url]) andReturnValue:@(YES)]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + XCTAssertTrue([self.fakeApplicationDelegate application:self.mockApplication + openURL:url + sourceApplication:@"" + annotation:[[NSObject alloc] init]]); +#pragma clang diagnostic pop + + [self.mockAuthURLPresenter verify]; +} + +#endif // TARGET_OS_IOS + #pragma mark - Interoperability Tests /** @fn testComponentsBeingRegistered @@ -2552,4 +2645,12 @@ - (void)waitForTimeIntervel:(NSTimeInterval)timeInterval { [self waitForExpectationsWithTimeout:timeInterval + kExpectationTimeout handler:nil]; } +- (void)waitForAuthGlobalWorkQueueDrain { + dispatch_semaphore_t workerSemaphore = dispatch_semaphore_create(0); + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + dispatch_semaphore_signal(workerSemaphore); + }); + dispatch_semaphore_wait(workerSemaphore, DISPATCH_TIME_FOREVER /*DISPATCH_TIME_NOW + 10 * NSEC_PER_SEC*/); +} + @end diff --git a/Example/Auth/Tests/FIRAuthUserDefaultsStorageTests.m b/Example/Auth/Tests/FIRAuthUserDefaultsStorageTests.m index 07493d5d8ef..e7e081b3e22 100644 --- a/Example/Auth/Tests/FIRAuthUserDefaultsStorageTests.m +++ b/Example/Auth/Tests/FIRAuthUserDefaultsStorageTests.m @@ -18,8 +18,6 @@ #import "FIRAuthUserDefaultsStorage.h" -#if FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE - NS_ASSUME_NONNULL_BEGIN /** @var kKey @@ -151,5 +149,3 @@ - (void)testStandardUserDefaults { @end NS_ASSUME_NONNULL_END - -#endif // FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE diff --git a/Example/Auth/Tests/FIROAuthProviderTests.m b/Example/Auth/Tests/FIROAuthProviderTests.m index 516b653c5d9..3284704c804 100644 --- a/Example/Auth/Tests/FIROAuthProviderTests.m +++ b/Example/Auth/Tests/FIROAuthProviderTests.m @@ -80,7 +80,7 @@ static NSString *const kFakeReverseClientID = @"com.googleusercontent.apps.123456"; /** @var kFakeOAuthResponseURL - @brief A fake OAuth reponse URL used in test. + @brief A fake OAuth response URL used in test. */ static NSString *const kFakeOAuthResponseURL = @"fakeOAuthResponseURL"; diff --git a/Example/Auth/Tests/FIRUserTests.m b/Example/Auth/Tests/FIRUserTests.m index e05e9a012df..596e6ee0bf5 100644 --- a/Example/Auth/Tests/FIRUserTests.m +++ b/Example/Auth/Tests/FIRUserTests.m @@ -1257,7 +1257,8 @@ - (void)testReauthenticateSuccess { }); FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; - [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) { + [user reauthenticateWithCredential:emailCredential completion:^(FIRAuthDataResult * _Nullable result, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); // Verify that the current user is unchanged. @@ -1285,9 +1286,9 @@ - (void)testReauthenticateAndRetrieveDataSuccess { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *googleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:googleCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:googleCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserGoogle:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1305,10 +1306,9 @@ - (void)testReauthenticateAndRetrieveDataSuccess { FIRAuthCredential *reauthenticateGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [authResult.user - reauthenticateAndRetrieveDataWithCredential:reauthenticateGoogleCredential - completion:^(FIRAuthDataResult *_Nullable - reauthenticateAuthResult, - NSError *_Nullable error) { + reauthenticateWithCredential:reauthenticateGoogleCredential + completion:^(FIRAuthDataResult *_Nullable reauthenticateAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); // Verify that the current user is unchanged. @@ -1373,7 +1373,8 @@ - (void)testReauthenticateFailure { }); FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; - [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) { + [user reauthenticateWithCredential:emailCredential completion:^(FIRAuthDataResult * _Nullable result, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); // Verify user mismatch error. XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch); @@ -1408,7 +1409,9 @@ - (void)testReauthenticateUserMismatchFailure { }); FIRAuthCredential *googleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; - [user reauthenticateWithCredential:googleCredential completion:^(NSError *_Nullable error) { + [user reauthenticateWithCredential:googleCredential + completion:^(FIRAuthDataResult * _Nullable result, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); // Verify user mismatch error. XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch); @@ -1437,9 +1440,9 @@ - (void)testlinkAndRetrieveDataSuccess { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1456,10 +1459,9 @@ - (void)testlinkAndRetrieveDataSuccess { FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; - [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [authResult.user linkWithCredential:linkGoogleCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); // Verify that the current user is unchanged. @@ -1498,9 +1500,9 @@ - (void)testlinkAndRetrieveDataError { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1513,16 +1515,16 @@ - (void)testlinkAndRetrieveDataError { FIRVerifyAssertionResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { callback(nil, - [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:kEmail]); + [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:kEmail + updatedCredential:nil]); }); }); FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; - [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [authResult.user linkWithCredential:linkGoogleCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeAccountExistsWithDifferentCredential); @@ -1550,9 +1552,9 @@ - (void)testlinkAndRetrieveDataProviderAlreadyLinked { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1562,10 +1564,9 @@ - (void)testlinkAndRetrieveDataProviderAlreadyLinked { FIRAuthCredential *linkFacebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [authResult.user linkAndRetrieveDataWithCredential:linkFacebookCredential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [authResult.user linkWithCredential:linkFacebookCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); @@ -1592,9 +1593,9 @@ - (void)testlinkAndRetrieveDataErrorAutoSignOut { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1612,10 +1613,9 @@ - (void)testlinkAndRetrieveDataErrorAutoSignOut { FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; - [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [authResult.user linkWithCredential:linkGoogleCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); @@ -1671,9 +1671,9 @@ - (void)testLinkingAnonymousAccountsUpdatesIsAnonymous { }); XCTAssertTrue(user.isAnonymous); - [user linkAndRetrieveDataWithCredential:linkEmailCredential - completion:^(FIRAuthDataResult *_Nullable linkAuthResult, - NSError *_Nullable error) { + [user linkWithCredential:linkEmailCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(user.email, kEmail); @@ -1701,9 +1701,9 @@ - (void)testlinkEmailAndRetrieveDataSuccess { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1736,10 +1736,9 @@ - (void)testlinkEmailAndRetrieveDataSuccess { FIRAuthCredential *linkEmailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; - [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [authResult.user linkWithCredential:linkEmailCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(linkAuthResult.user.email, kEmail); @@ -1769,9 +1768,9 @@ - (void)testlinkEmailProviderAlreadyLinkedError { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1804,19 +1803,17 @@ - (void)testlinkEmailProviderAlreadyLinkedError { FIRAuthCredential *linkEmailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; - [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [authResult.user linkWithCredential:linkEmailCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertNil(error); XCTAssertEqualObjects(linkAuthResult.user.email, kEmail); XCTAssertEqualObjects(linkAuthResult.user.displayName, kEmailDisplayName); // Try linking same credential a second time to trigger client side error. - [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [authResult.user linkWithCredential:linkEmailCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); @@ -1845,9 +1842,9 @@ - (void)testlinkEmailAndRetrieveDataError { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1866,11 +1863,10 @@ - (void)testlinkEmailAndRetrieveDataError { }); FIRAuthCredential *linkEmailCredential = - [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; - [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; + [authResult.user linkWithCredential:linkEmailCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests); @@ -1898,9 +1894,9 @@ - (void)testlinkEmailAndRetrieveDataErrorAutoSignOut { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1920,10 +1916,9 @@ - (void)testlinkEmailAndRetrieveDataErrorAutoSignOut { FIRAuthCredential *linkEmailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; - [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [authResult.user linkWithCredential:linkEmailCredential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired); @@ -1951,9 +1946,9 @@ - (void)testlinkCredentialSuccess { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -1971,10 +1966,10 @@ - (void)testlinkCredentialSuccess { FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [authResult.user linkWithCredential:linkGoogleCredential - completion:^(FIRUser *_Nullable user, + completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { XCTAssertNil(error); - id userInfo = user.providerData.firstObject; + id userInfo = result.user.providerData.firstObject; XCTAssertEqual(userInfo.providerID, FIRGoogleAuthProviderID); XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); [expectation fulfill]; @@ -2001,9 +1996,9 @@ - (void)testlinkCredentialError { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -2022,9 +2017,9 @@ - (void)testlinkCredentialError { FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [authResult.user linkWithCredential:linkGoogleCredential - completion:^(FIRUser *_Nullable user, + completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { - XCTAssertNil(user); + XCTAssertNil(result.user); XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); [expectation fulfill]; }]; @@ -2049,9 +2044,9 @@ - (void)testlinkCredentialProviderAlreadyLinkedError { [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; - [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); @@ -2062,9 +2057,9 @@ - (void)testlinkCredentialProviderAlreadyLinkedError { FIRAuthCredential *linkFacebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kGoogleAccessToken]; [authResult.user linkWithCredential:linkFacebookCredential - completion:^(FIRUser *_Nullable user, + completion:^(FIRAuthDataResult * _Nullable result, NSError *_Nullable error) { - XCTAssertNil(user); + XCTAssertNil(result.user); XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); [expectation fulfill]; }]; @@ -2105,10 +2100,8 @@ - (void)testlinkPhoneAuthCredentialSuccess { FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; - [user linkAndRetrieveDataWithCredential:credential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [user linkWithCredential:credential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertNil(error); XCTAssertEqualObjects([FIRAuth auth].currentUser.providerData.firstObject.providerID, FIRPhoneAuthProviderID); @@ -2170,10 +2163,8 @@ - (void)testUnlinkPhoneAuthCredentialSuccess { [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; // Link phone credential. - [user linkAndRetrieveDataWithCredential:credential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [user linkWithCredential:credential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertNil(error); XCTAssertEqualObjects([FIRAuth auth].currentUser.providerData.firstObject.providerID, FIRPhoneAuthProviderID); @@ -2216,10 +2207,8 @@ - (void)testlinkPhoneAuthCredentialFailure { FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; - [user linkAndRetrieveDataWithCredential:credential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [user linkWithCredential:credential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); [expectation fulfill]; }]; @@ -2274,10 +2263,9 @@ - (void)testlinkPhoneCredentialAlreadyExistsError { FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; - [user linkAndRetrieveDataWithCredential:credential - completion:^(FIRAuthDataResult *_Nullable - linkAuthResult, - NSError *_Nullable error) { + [user linkWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable linkAuthResult, + NSError *_Nullable error) { XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeCredentialAlreadyInUse); FIRPhoneAuthCredential *credential = error.userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey]; diff --git a/Example/Core/Tests/FIRAnalyticsConfigurationTest.m b/Example/Core/Tests/FIRAnalyticsConfigurationTest.m index 43babb0f544..7fb579f1172 100644 --- a/Example/Core/Tests/FIRAnalyticsConfigurationTest.m +++ b/Example/Core/Tests/FIRAnalyticsConfigurationTest.m @@ -16,7 +16,6 @@ #import "FIRTestCase.h" -#import #import @interface FIRAnalyticsConfigurationTest : FIRTestCase @@ -48,73 +47,6 @@ - (void)testSharedInstance { XCTAssertNotNil(analyticsConfig); } -/// Test that setting the minimum session interval on the singleton fires a notification. -- (void)testMinimumSessionIntervalNotification { - // Pick a value to set as the session interval and verify it's in the userInfo dictionary of the - // posted notification. - NSNumber *sessionInterval = @2601; - - // Set up the expectation for the notification. - FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance]; - NSString *notificationName = kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification; - [self expectNotificationForObserver:self.observerMock - notificationName:notificationName - object:config - userInfo:@{notificationName : sessionInterval}]; - - // Trigger the notification. - [config setMinimumSessionInterval:[sessionInterval integerValue]]; - - // Verify the observer mock. - OCMVerifyAll(self.observerMock); -} - -/// Test that setting the minimum session timeout interval on the singleton fires a notification. -- (void)testSessionTimeoutIntervalNotification { - // Pick a value to set as the timeout interval and verify it's in the userInfo dictionary of the - // posted notification. - NSNumber *timeoutInterval = @1000; - - // Set up the expectation for the notification. - FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance]; - NSString *notificationName = kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification; - [self expectNotificationForObserver:self.observerMock - notificationName:notificationName - object:config - userInfo:@{notificationName : timeoutInterval}]; - - // Trigger the notification. - [config setSessionTimeoutInterval:[timeoutInterval integerValue]]; - - /// Verify the observer mock. - OCMVerifyAll(self.observerMock); -} - -- (void)testSettingAnalyticsCollectionEnabled { - // Test setting to enabled. The ordering matters for these notifications. - FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance]; - NSString *notificationName = kFIRAnalyticsConfigurationSetEnabledNotification; - [self.notificationCenter addMockObserver:self.observerMock name:notificationName object:config]; - - [self.observerMock setExpectationOrderMatters:YES]; - [[self.observerMock expect] notificationWithName:notificationName - object:config - userInfo:@{notificationName : @YES}]; - - // Test setting to enabled. - [config setAnalyticsCollectionEnabled:YES]; - - // Expect the second notification. - [[self.observerMock expect] notificationWithName:notificationName - object:config - userInfo:@{notificationName : @NO}]; - - // Test setting to disabled. - [config setAnalyticsCollectionEnabled:NO]; - - OCMVerifyAll(self.observerMock); -} - - (void)testSettingAnalyticsCollectionPersistence { id userDefaultsMock = OCMPartialMock([NSUserDefaults standardUserDefaults]); FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance]; diff --git a/Example/Core/Tests/FIRAppTest.m b/Example/Core/Tests/FIRAppTest.m index c144d6f438b..1a273e37f01 100644 --- a/Example/Core/Tests/FIRAppTest.m +++ b/Example/Core/Tests/FIRAppTest.m @@ -15,7 +15,7 @@ #import "FIRTestCase.h" #import "FIRTestComponents.h" -#import +#import #import #import @@ -258,13 +258,13 @@ - (void)testDeleteApp { } - (void)testErrorForSubspecConfigurationFailure { - NSError *error = [FIRApp errorForSubspecConfigurationFailureWithDomain:kFirebaseAdMobErrorDomain - errorCode:FIRErrorCodeAdMobFailed - service:kFIRServiceAdMob + NSError *error = [FIRApp errorForSubspecConfigurationFailureWithDomain:kFirebaseCoreErrorDomain + errorCode:-38 + service:kFIRServiceAuth reason:@"some reason"]; XCTAssertNotNil(error); - XCTAssert([error.domain isEqualToString:kFirebaseAdMobErrorDomain]); - XCTAssert(error.code == FIRErrorCodeAdMobFailed); + XCTAssert([error.domain isEqualToString:kFirebaseCoreErrorDomain]); + XCTAssert(error.code == -38); XCTAssert([error.description containsString:@"Configuration failed for"]); } @@ -389,11 +389,11 @@ - (void)testAppIDFormatInvalid { // Some direct tests of the validateAppIDFormat:withVersion: method. // Sanity checks first. NSString *const kGoodAppIDV1 = @"1:1337:ios:deadbeef"; - NSString *const kGoodVersionV1 = @"1:"; + NSString *const kGoodVersionV1 = @"1"; XCTAssertTrue([FIRApp validateAppIDFormat:kGoodAppIDV1 withVersion:kGoodVersionV1]); NSString *const kGoodAppIDV2 = @"2:1337:ios:5e18052ab54fbfec"; - NSString *const kGoodVersionV2 = @"2:"; + NSString *const kGoodVersionV2 = @"2"; XCTAssertTrue([FIRApp validateAppIDFormat:kGoodAppIDV2 withVersion:kGoodVersionV2]); // Version mismatch. @@ -440,18 +440,13 @@ - (void)testAppIDFingerprintInvalid { // Some direct tests of the validateAppIDFingerprint:withVersion: method. // Sanity checks first. NSString *const kGoodAppIDV1 = @"1:1337:ios:deadbeef"; - NSString *const kGoodVersionV1 = @"1:"; + NSString *const kGoodVersionV1 = @"1"; XCTAssertTrue([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:kGoodVersionV1]); NSString *const kGoodAppIDV2 = @"2:1337:ios:5e18052ab54fbfec"; - NSString *const kGoodVersionV2 = @"2:"; + NSString *const kGoodVersionV2 = @"2"; XCTAssertTrue([FIRApp validateAppIDFormat:kGoodAppIDV2 withVersion:kGoodVersionV2]); - // Version mismatch. - XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV2 withVersion:kGoodVersionV1]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:kGoodVersionV2]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:@"999:"]); - // Nil or empty strings. XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:nil]); XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:@""]); @@ -464,35 +459,19 @@ - (void)testAppIDFingerprintInvalid { XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodVersionV1 withVersion:kGoodVersionV1]); // The version is the entire app ID. XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:kGoodAppIDV1]); - - // Versions digits that may make a partial match. - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"01:1337:ios:deadbeef" - withVersion:kGoodVersionV1]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"10:1337:ios:deadbeef" - withVersion:kGoodVersionV1]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"11:1337:ios:deadbeef" - withVersion:kGoodVersionV1]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"21:1337:ios:5e18052ab54fbfec" - withVersion:kGoodVersionV2]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"22:1337:ios:5e18052ab54fbfec" - withVersion:kGoodVersionV2]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"02:1337:ios:5e18052ab54fbfec" - withVersion:kGoodVersionV2]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"20:1337:ios:5e18052ab54fbfec" - withVersion:kGoodVersionV2]); - // Extra fields. - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"ab:1:1337:ios:deadbeef" - withVersion:kGoodVersionV1]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"1:ab:1337:ios:deadbeef" - withVersion:kGoodVersionV1]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"1:1337:ab:ios:deadbeef" - withVersion:kGoodVersionV1]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"1:1337:ios:ab:deadbeef" - withVersion:kGoodVersionV1]); - XCTAssertFalse([FIRApp validateAppIDFingerprint:@"1:1337:ios:deadbeef:ab" - withVersion:kGoodVersionV1]); } +// Uncomment if you need to measure performance of [FIRApp validateAppID:]. +// It is commented because measures are heavily dependent on a build agent configuration, +// so it cannot produce reliable resault on CI +//- (void)testAppIDFingerprintPerfomance { +// [self measureBlock:^{ +// for (NSInteger i = 0; i < 100; ++i) { +// [self testAppIDPrefix]; +// } +// }]; +//} + #pragma mark - Automatic Data Collection Tests - (void)testGlobalDataCollectionNoFlags { diff --git a/Example/Core/Tests/FIRBundleUtilTest.m b/Example/Core/Tests/FIRBundleUtilTest.m index 1204a8945fc..ad0669ddf54 100644 --- a/Example/Core/Tests/FIRBundleUtilTest.m +++ b/Example/Core/Tests/FIRBundleUtilTest.m @@ -15,6 +15,7 @@ #import "FIRTestCase.h" #import +#import static NSString *const kResultPath = @"resultPath"; static NSString *const kResourceName = @"resourceName"; @@ -69,16 +70,53 @@ - (void)testFindOptionsDictionaryPath_secondBundle { - (void)testBundleIdentifierExistsInBundles { NSString *bundleID = @"com.google.test"; [OCMStub([self.mockBundle bundleIdentifier]) andReturn:bundleID]; - XCTAssertTrue([FIRBundleUtil hasBundleIdentifier:bundleID inBundles:@[ self.mockBundle ]]); + XCTAssertTrue([FIRBundleUtil hasBundleIdentifierPrefix:bundleID inBundles:@[ self.mockBundle ]]); } - (void)testBundleIdentifierExistsInBundles_notExist { [OCMStub([self.mockBundle bundleIdentifier]) andReturn:@"com.google.test"]; - XCTAssertFalse([FIRBundleUtil hasBundleIdentifier:@"not-exist" inBundles:@[ self.mockBundle ]]); + XCTAssertFalse([FIRBundleUtil hasBundleIdentifierPrefix:@"not-exist" + inBundles:@[ self.mockBundle ]]); } - (void)testBundleIdentifierExistsInBundles_emptyBundlesArray { - XCTAssertFalse([FIRBundleUtil hasBundleIdentifier:@"com.google.test" inBundles:@[]]); + XCTAssertFalse([FIRBundleUtil hasBundleIdentifierPrefix:@"com.google.test" inBundles:@[]]); +} + +- (void)testBundleIdentifierHasPrefixInBundlesForExtension { + id environmentUtilsMock = [OCMockObject mockForClass:[GULAppEnvironmentUtil class]]; + [[[environmentUtilsMock stub] andReturnValue:@(YES)] isAppExtension]; + + [OCMStub([self.mockBundle bundleIdentifier]) andReturn:@"com.google.test"]; + XCTAssertTrue([FIRBundleUtil hasBundleIdentifierPrefix:@"com.google.test.someextension" + inBundles:@[ self.mockBundle ]]); + + [environmentUtilsMock stopMocking]; +} + +- (void)testBundleIdentifierHasPrefixInBundlesNotValidExtension { + id environmentUtilsMock = [OCMockObject mockForClass:[GULAppEnvironmentUtil class]]; + [[[environmentUtilsMock stub] andReturnValue:@(YES)] isAppExtension]; + + [OCMStub([self.mockBundle bundleIdentifier]) andReturn:@"com.google.test"]; + XCTAssertFalse([FIRBundleUtil hasBundleIdentifierPrefix:@"com.google.test.someextension.some" + inBundles:@[ self.mockBundle ]]); + + XCTAssertFalse([FIRBundleUtil hasBundleIdentifierPrefix:@"com.google.testsomeextension" + inBundles:@[ self.mockBundle ]]); + + XCTAssertFalse([FIRBundleUtil hasBundleIdentifierPrefix:@"com.google.testsomeextension.some" + inBundles:@[ self.mockBundle ]]); + + XCTAssertFalse([FIRBundleUtil hasBundleIdentifierPrefix:@"not-exist" + inBundles:@[ self.mockBundle ]]); + + // Should be NO, since if @"com.google.tests" is an app extension identifier, then the app bundle + // identifier is @"com.google" + XCTAssertFalse([FIRBundleUtil hasBundleIdentifierPrefix:@"com.google.tests" + inBundles:@[ self.mockBundle ]]); + + [environmentUtilsMock stopMocking]; } @end diff --git a/Example/Core/Tests/FIRConfigurationTest.m b/Example/Core/Tests/FIRConfigurationTest.m index 2b3ff463139..8045104b8bb 100644 --- a/Example/Core/Tests/FIRConfigurationTest.m +++ b/Example/Core/Tests/FIRConfigurationTest.m @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "FIRConfiguration.h" +#import #import "FIRTestCase.h" diff --git a/Example/Core/Tests/FIRLoggerTest.m b/Example/Core/Tests/FIRLoggerTest.m index a4415c047e1..7f9e1a783c0 100644 --- a/Example/Core/Tests/FIRLoggerTest.m +++ b/Example/Core/Tests/FIRLoggerTest.m @@ -12,14 +12,260 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import +#ifdef DEBUG +// The tests depend upon library methods only built with #ifdef DEBUG #import "FIRTestCase.h" +// TODO - FIRLoggerTest should be split into a separate FIRLoggerTest and GULLoggerTest. +// No test should include both includes. +#import +#import + +#import + +// The following constants are exposed from FIRLogger for unit tests. +extern NSString *const kFIRDisableDebugModeApplicationArgument; +extern NSString *const kFIREnableDebugModeApplicationArgument; + +/// Key for the debug mode bit in NSUserDefaults. +extern NSString *const kFIRPersistedDebugModeKey; + +extern const char *kGULLoggerASLClientFacilityName; + +extern void FIRResetLogger(void); + +extern void FIRSetLoggerUserDefaults(NSUserDefaults *defaults); + +extern aslclient getGULLoggerClient(void); + +extern dispatch_queue_t getGULClientQueue(void); + +extern BOOL getGULLoggerDebugMode(void); + +static NSString *const kMessageCode = @"I-COR000001"; + @interface FIRLoggerTest : FIRTestCase + +@property(nonatomic) NSString *randomLogString; + +@property(nonatomic, strong) NSUserDefaults *defaults; + @end @implementation FIRLoggerTest -// TODO(bstpierre): Test FIRLogger functionality separate from GULLogger. +- (void)setUp { + [super setUp]; + FIRResetLogger(); + + // Stub NSUserDefaults for cleaner testing. + _defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.firebase.logger_test"]; + FIRSetLoggerUserDefaults(_defaults); +} + +- (void)tearDown { + [super tearDown]; + + _defaults = nil; +} + +// Test some stable variables to make sure they weren't accidently changed. +- (void)testStableVariables { + // Strings of type FIRLoggerServices. + XCTAssertEqualObjects(kFIRLoggerABTesting, @"[Firebase/ABTesting]"); + XCTAssertEqualObjects(kFIRLoggerAdMob, @"[Firebase/AdMob]"); + XCTAssertEqualObjects(kFIRLoggerAnalytics, @"[Firebase/Analytics]"); + XCTAssertEqualObjects(kFIRLoggerCore, @"[Firebase/Core]"); + XCTAssertEqualObjects(kFIRLoggerMLKit, @"[Firebase/MLKit]"); + XCTAssertEqualObjects(kFIRLoggerRemoteConfig, @"[Firebase/RemoteConfig]"); +} + +- (void)testInitializeASLForNonDebugMode { + // Stub. + id processInfoMock = [OCMockObject partialMockForObject:[NSProcessInfo processInfo]]; + NSArray *arguments = @[ kFIRDisableDebugModeApplicationArgument ]; + [[[processInfoMock stub] andReturn:arguments] arguments]; + + // Test. + FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error."); + + // Assert. +#if MAKE_THREAD_SAFE + NSNumber *debugMode = [self.defaults objectForKey:kFIRPersistedDebugModeKey]; + XCTAssertNil(debugMode); + XCTAssertFalse(getGULLoggerDebugMode()); +#endif + + // Stop. + [processInfoMock stopMocking]; +} + +- (void)testInitializeASLForDebugModeWithArgument { + // Stub. + id processInfoMock = [OCMockObject partialMockForObject:[NSProcessInfo processInfo]]; + NSArray *arguments = @[ kFIREnableDebugModeApplicationArgument ]; + [[[processInfoMock stub] andReturn:arguments] arguments]; + + // Test. + FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error."); + +#ifdef MAKE_THREAD_SAFE + // Assert. + NSNumber *debugMode = [self.defaults objectForKey:kGULPersistedDebugModeKey]; + XCTAssertTrue(debugMode.boolValue); + XCTAssertTrue(getGULLoggerDebugMode()); +#endif + + // Stop. + [processInfoMock stopMocking]; +} + +- (void)testInitializeASLForDebugModeWithUserDefaults { + // Stub. + NSNumber *debugMode = @YES; + [self.defaults setBool:debugMode.boolValue forKey:kFIRPersistedDebugModeKey]; + + // Test. + GULLogError(@"my service", NO, kMessageCode, @"Some error."); + + // Assert. + debugMode = [self.defaults objectForKey:kFIRPersistedDebugModeKey]; + XCTAssertTrue(debugMode.boolValue); +} + +- (void)testMessageCodeFormat { + // Valid case. + XCTAssertNoThrow(FIRLogError(kFIRLoggerCore, @"I-APP000001", @"Message.")); + + // An extra dash or missing dash should fail. + XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"I-APP-000001", @"Message.")); + XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"IAPP000001", @"Message.")); + + // Wrong number of digits should fail. + XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"I-APP00001", @"Message.")); + XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"I-APP0000001", @"Message.")); + + // Lowercase should fail. + XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"I-app000001", @"Message.")); + +// nil or empty message code should fail. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + XCTAssertThrows(FIRLogError(kFIRLoggerCore, nil, @"Message.")); +#pragma clang diagnostic pop + + XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"", @"Message.")); + + // Android message code should fail. + XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"A-APP000001", @"Message.")); +} + +- (void)testLoggerInterface { + XCTAssertNoThrow(FIRLogError(kFIRLoggerCore, kMessageCode, @"Message.")); + XCTAssertNoThrow(FIRLogError(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah")); + + XCTAssertNoThrow(FIRLogWarning(kFIRLoggerCore, kMessageCode, @"Message.")); + XCTAssertNoThrow(FIRLogWarning(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah")); + + XCTAssertNoThrow(FIRLogNotice(kFIRLoggerCore, kMessageCode, @"Message.")); + XCTAssertNoThrow(FIRLogNotice(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah")); + + XCTAssertNoThrow(FIRLogInfo(kFIRLoggerCore, kMessageCode, @"Message.")); + XCTAssertNoThrow(FIRLogInfo(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah")); + + XCTAssertNoThrow(FIRLogDebug(kFIRLoggerCore, kMessageCode, @"Message.")); + XCTAssertNoThrow(FIRLogDebug(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah")); +} + +// asl_set_filter does not perform as expected in unit test environment with simulator. The +// following test only checks whether the logs have been sent to system with the default settings in +// the unit test environment. +- (void)testSystemLogWithDefaultStatus { +#if !(BUG128) // Disable until https://github.com/firebase/firebase-ios-sdk/issues/128 is fixed + // Test fails on device and iOS 9 simulators - b/38130372 + return; +#else + // Sets the time interval that we need to wait in order to fetch all the logs. + NSTimeInterval timeInterval = 0.1f; + // Generates a random string each time and check whether it has been logged. + // Log messages with Notice level and below should be logged to system/device by default. + self.randomLogString = [NSUUID UUID].UUIDString; + FIRLogError(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertTrue([self logExists]); + + self.randomLogString = [NSUUID UUID].UUIDString; + FIRLogWarning(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertTrue([self logExists]); + + self.randomLogString = [NSUUID UUID].UUIDString; + FIRLogNotice(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertTrue([self logExists]); + + // Log messages with Info level and above should NOT be logged to system/device by default. + self.randomLogString = [NSUUID UUID].UUIDString; + FIRLogInfo(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertFalse([self logExists]); + + self.randomLogString = [NSUUID UUID].UUIDString; + FIRLogDebug(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertFalse([self logExists]); +#endif +} + +// The FIRLoggerLevel enum must match the ASL_LEVEL_* constants, but we manually redefine +// them in FIRLoggerLevel.h since we cannot include (see b/34976089 for more details). +// This test ensures the constants match. +- (void)testFIRLoggerLevelValues { + XCTAssertEqual(FIRLoggerLevelError, ASL_LEVEL_ERR); + XCTAssertEqual(FIRLoggerLevelWarning, ASL_LEVEL_WARNING); + XCTAssertEqual(FIRLoggerLevelNotice, ASL_LEVEL_NOTICE); + XCTAssertEqual(FIRLoggerLevelInfo, ASL_LEVEL_INFO); + XCTAssertEqual(FIRLoggerLevelDebug, ASL_LEVEL_DEBUG); +} + +// Helper functions. +- (BOOL)logExists { + [self drainFIRClientQueue]; + NSString *correctMsg = + [NSString stringWithFormat:@"%@[%@] %@", kFIRLoggerCore, kMessageCode, self.randomLogString]; + return [self messageWasLogged:correctMsg]; +} + +- (void)drainFIRClientQueue { + dispatch_semaphore_t workerSemaphore = dispatch_semaphore_create(0); + dispatch_async(getGULClientQueue(), ^{ + dispatch_semaphore_signal(workerSemaphore); + }); + dispatch_semaphore_wait(workerSemaphore, DISPATCH_TIME_FOREVER); +} + +- (BOOL)messageWasLogged:(NSString *)message { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + aslmsg query = asl_new(ASL_TYPE_QUERY); + asl_set_query(query, ASL_KEY_FACILITY, kGULLoggerASLClientFacilityName, ASL_QUERY_OP_EQUAL); + aslresponse r = asl_search(getGULLoggerClient(), query); + asl_free(query); + aslmsg m; + const char *val; + NSMutableArray *allMsg = [[NSMutableArray alloc] init]; + while ((m = asl_next(r)) != NULL) { + val = asl_get(m, ASL_KEY_MSG); + if (val) { + [allMsg addObject:[NSString stringWithUTF8String:val]]; + } + } + asl_free(m); + asl_release(r); + return [allMsg containsObject:message]; +#pragma clang pop +} + @end +#endif diff --git a/Example/Core/Tests/FIROptionsTest.m b/Example/Core/Tests/FIROptionsTest.m index 3c078d93a43..e1f1406924b 100644 --- a/Example/Core/Tests/FIROptionsTest.m +++ b/Example/Core/Tests/FIROptionsTest.m @@ -575,6 +575,7 @@ - (void)testVersionFormat { XCTAssertEqual(numberOfMatches, 1, @"Incorrect library version format."); } +// TODO: The version test will break when the Firebase major version hits 10. - (void)testVersionConsistency { const char *versionString = [kFIRLibraryVersionID UTF8String]; int major = versionString[0] - '0'; @@ -584,4 +585,17 @@ - (void)testVersionConsistency { XCTAssertEqualObjects(str, [NSString stringWithUTF8String:(const char *)FIRCoreVersionString]); } +// Repeat test with more Objective-C. +// TODO: The version test will break when the Firebase major version hits 10. +- (void)testVersionConsistency2 { + NSRange major = NSMakeRange(0, 1); + NSRange minor = NSMakeRange(1, 2); + NSRange patch = NSMakeRange(3, 2); + NSString *str = + [NSString stringWithFormat:@"%@.%d.%d", [kFIRLibraryVersionID substringWithRange:major], + [[kFIRLibraryVersionID substringWithRange:minor] intValue], + [[kFIRLibraryVersionID substringWithRange:patch] intValue]]; + XCTAssertEqualObjects(str, [NSString stringWithUTF8String:(const char *)FIRCoreVersionString]); +} + @end diff --git a/Example/DynamicLinks/Tests/FDLURLComponentsTests.m b/Example/DynamicLinks/Tests/FDLURLComponentsTests.m index dee0874c6f5..e9d339a4c87 100644 --- a/Example/DynamicLinks/Tests/FDLURLComponentsTests.m +++ b/Example/DynamicLinks/Tests/FDLURLComponentsTests.m @@ -528,7 +528,7 @@ - (void)testFDLComponentsNotNilOnDomainWithHTTPScheme { NSURL *link = [NSURL URLWithString:linkString]; FIRDynamicLinkComponents *components = - [FIRDynamicLinkComponents componentsWithLink:link domain:@"http://xyz.page.link"]; + [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:@"https://xyz.page.link"]; XCTAssertNotNil(components); } @@ -538,7 +538,7 @@ - (void)testFDLComponentsNotNilOnDomainWithHTTPSScheme { NSURL *link = [NSURL URLWithString:linkString]; FIRDynamicLinkComponents *components = - [FIRDynamicLinkComponents componentsWithLink:link domain:@"https://xyz.page.link"]; + [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:@"https://xyz.page.link"]; XCTAssertNotNil(components); } @@ -549,10 +549,9 @@ - (void)testFDLComponentsFailsOnMalformedDomain { FIRDynamicLinkComponents *components = [FIRDynamicLinkComponents componentsWithLink:link - domain:@"this is invalid domain URI Prefix"]; + domainURIPrefix:@"this is invalid domain URI Prefix"]; - XCTAssertNotNil(components); - XCTAssertNil(components.url); + XCTAssertNil(components); } - (void)testFDLComponentsCreatesFullLinkCorrectly { @@ -708,79 +707,6 @@ - (void)testShortenURL { [componentsClassMock stopMocking]; } -- (void)testDeprecatedMethodComponentsWithLinkForDomain { - NSString *shortURLString = @"https://xyz.page.link/abcd"; - - // Mock key provider - id keyProviderClassMock = OCMClassMock([FIRDynamicLinkComponentsKeyProvider class]); - [[[keyProviderClassMock expect] andReturn:@"fake-api-key"] APIKey]; - - id componentsClassMock = OCMClassMock([FIRDynamicLinkComponents class]); - [[componentsClassMock expect] - sendHTTPRequest:OCMOCK_ANY - completion:[OCMArg checkWithBlock:^BOOL(id obj) { - void (^completion)(NSData *_Nullable, NSError *_Nullable) = obj; - NSDictionary *JSON = @{@"shortLink" : shortURLString}; - NSData *JSONData = [NSJSONSerialization dataWithJSONObject:JSON options:0 error:0]; - completion(JSONData, nil); - return YES; - }]]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - NSURL *link = [NSURL URLWithString:@"https://google.com/abc"]; - FIRDynamicLinkComponents *components = - [FIRDynamicLinkComponents componentsWithLink:link domain:@"xyz.page.link"]; - [components - shortenWithCompletion:^(NSURL *_Nullable shortURL, NSArray *_Nullable warnings, - NSError *_Nullable error) { - XCTAssertEqualObjects(shortURL.absoluteString, shortURLString); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:0.1 handler:nil]; - - [keyProviderClassMock verify]; - [keyProviderClassMock stopMocking]; - [componentsClassMock verify]; - [componentsClassMock stopMocking]; -} - -- (void)testDeprecatedMethodComponentsWithLinkForDomainWithInvalidDomainScheme { - NSString *shortURLString = @"https://xyz.page.link/abcd"; - - // Mock key provider - id keyProviderClassMock = OCMClassMock([FIRDynamicLinkComponentsKeyProvider class]); - [[[keyProviderClassMock expect] andReturn:@"fake-api-key"] APIKey]; - - id componentsClassMock = OCMClassMock([FIRDynamicLinkComponents class]); - [[componentsClassMock expect] - sendHTTPRequest:OCMOCK_ANY - completion:[OCMArg checkWithBlock:^BOOL(id obj) { - void (^completion)(NSData *_Nullable, NSError *_Nullable) = obj; - NSDictionary *JSON = @{@"shortLink" : shortURLString}; - NSData *JSONData = [NSJSONSerialization dataWithJSONObject:JSON options:0 error:0]; - completion(JSONData, nil); - return YES; - }]]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; - NSURL *link = [NSURL URLWithString:@"https://google.com/abc"]; - FIRDynamicLinkComponents *components = - [FIRDynamicLinkComponents componentsWithLink:link domain:@"http://xyz.page.link"]; - XCTAssertNotNil(components); - [components - shortenWithCompletion:^(NSURL *_Nullable shortURL, NSArray *_Nullable warnings, - NSError *_Nullable error) { - XCTAssertEqualObjects(shortURL.absoluteString, shortURLString); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:0.1 handler:nil]; - - [keyProviderClassMock verify]; - [keyProviderClassMock stopMocking]; - [componentsClassMock verify]; - [componentsClassMock stopMocking]; -} - - (void)testShortenURLReturnsErrorWhenAPIKeyMissing { NSString *shortURLString = @"https://xyz.page.link/abcd"; diff --git a/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m b/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m index 74fc2373d78..17df2e05fe2 100644 --- a/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m +++ b/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m @@ -21,6 +21,7 @@ #import #import #import "DynamicLinks/FIRDLRetrievalProcessFactory.h" +#import "DynamicLinks/FIRDLRetrievalProcessResult+Private.h" #import "DynamicLinks/FIRDynamicLink+Private.h" #import "DynamicLinks/FIRDynamicLinkNetworking+Private.h" #import "DynamicLinks/FIRDynamicLinks+FirstParty.h" @@ -76,6 +77,7 @@ - (BOOL)setUpWithLaunchOptions:(nullable NSDictionary *)launchOptions clientID:(NSString *)clientID urlScheme:(nullable NSString *)urlScheme userDefaults:(nullable NSUserDefaults *)userDefaults; +- (BOOL)canParseUniversalLinkURL:(nullable NSURL *)url; @end @interface FakeShortLinkResolver : FIRDynamicLinkNetworking @@ -773,13 +775,90 @@ - (void)testUniversalLinkWithSubdomain_DeepLinkWithParameters { XCTAssertEqualObjects(dynamicLink.url.absoluteString, parsedDeepLinkString); } -- (void)testMatchesUnversalLinkWithLongDurableLink { - NSString *urlString = - @"https://sample.page.link?link=https://google.com/test&ibi=com.google.sample&ius=79306483"; - NSURL *url = [NSURL URLWithString:urlString]; - BOOL matchesShort = [self.service matchesShortLinkFormat:url]; +- (void)testMatchesShortLinkFormat { + NSArray *urlStrings = + @[ @"https://test.app.goo.gl/xyz", @"https://test.app.goo.gl/xyz?link=" ]; + + for (NSString *urlString in urlStrings) { + NSURL *url = [NSURL URLWithString:urlString]; + BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url]; - XCTAssertFalse(matchesShort, @"Long Durable Link should not match short link format"); + XCTAssertTrue(matchesShortLinkFormat, + @"Non-DDL domain URL matched short link format with URL: %@", url); + } +} + +// Custom domain entries in plist file: +// https://google.com +// https://google.com/one +// https://a.firebase.com/mypath +- (void)testFailMatchesShortLinkFormatForCustomDomains { + NSArray *urlStrings = @[ + @"https://google.com", + @"https://google.com?link=", + @"https://a.firebase.com", + @"https://a.firebase.com/mypath?link=", + ]; + + for (NSString *urlString in urlStrings) { + NSURL *url = [NSURL URLWithString:urlString]; + BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url]; + + XCTAssertFalse(matchesShortLinkFormat, + @"Non-DDL domain URL matched short link format with URL: %@", url); + } +} + +// Custom domain entries in plist file: +// https://google.com +// https://google.com/one +// https://a.firebase.com/mypath +- (void)testPassMatchesShortLinkFormatForCustomDomains { + NSArray *urlStrings = @[ + @"https://google.com/xyz", @"https://google.com/xyz/?link=", @"https://google.com/xyz?link=", + @"https://google.com/one/xyz", @"https://google.com/one/xyz?link=", + @"https://google.com/one?utm_campaignlink=", @"https://google.com/mylink", + @"https://google.com/one/mylink", @"https://a.firebase.com/mypath/mylink" + ]; + + for (NSString *urlString in urlStrings) { + NSURL *url = [NSURL URLWithString:urlString]; + BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url]; + + XCTAssertTrue(matchesShortLinkFormat, + @"Non-DDL domain URL matched short link format with URL: %@", url); + } +} + +- (void)testPassMatchesShortLinkFormat { + NSArray *urlStrings = @[ + @"https://test.app.goo.gl/xyz", + @"https://test.app.goo.gl/xyz?link=", + ]; + + for (NSString *urlString in urlStrings) { + NSURL *url = [NSURL URLWithString:urlString]; + BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url]; + + XCTAssertTrue(matchesShortLinkFormat, + @"Non-DDL domain URL matched short link format with URL: %@", url); + } +} + +- (void)testFailMatchesShortLinkFormat { + NSArray *urlStrings = @[ + @"https://test.app.goo.gl", @"https://test.app.goo.gl?link=", @"https://test.app.goo.gl/", + @"https://sample.page.link?link=https://google.com/test&ibi=com.google.sample&ius=79306483", + @"https://sample.page.link/?link=https://google.com/test&ibi=com.google.sample&ius=79306483" + ]; + + for (NSString *urlString in urlStrings) { + NSURL *url = [NSURL URLWithString:urlString]; + BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url]; + + XCTAssertFalse(matchesShortLinkFormat, + @"Non-DDL domain URL matched short link format with URL: %@", url); + } } - (void)testMatchesUnversalLinkWithShortDurableLink { @@ -967,6 +1046,49 @@ - (void)testCheckForPendingDynamicLinkReturnsImmediatelyIfAlreadyRead { [mockService stopMocking]; } +- (void)testRetrievalProcessResultURLContainsAllParametersPassedToDynamicLinkInitializer { + NSDictionary *linkParameters = @{ + @"deep_link_id" : @"https://mmaksym.com/test-app1", + @"match_message" : @"Link is uniquely matched for this device.", + @"match_type" : @"unique", + @"utm_campaign" : @"Maksym M Test", + @"utm_medium" : @"test_medium", + @"utm_source" : @"test_source", + @"a_parameter" : @"a_value" + }; + + FIRDynamicLink *dynamicLink = + [[FIRDynamicLink alloc] initWithParametersDictionary:linkParameters]; + FIRDLRetrievalProcessResult *result = + [[FIRDLRetrievalProcessResult alloc] initWithDynamicLink:dynamicLink + error:nil + message:nil + matchSource:nil]; + + NSURL *customSchemeURL = [result URLWithCustomURLScheme:@"scheme"]; + XCTAssertNotNil(customSchemeURL); + + // Validate URL parameters + NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:customSchemeURL + resolvingAgainstBaseURL:NO]; + XCTAssertNotNil(urlComponents); + XCTAssertEqualObjects(urlComponents.scheme, @"scheme"); + + NSMutableDictionary *notEncodedParameters = [linkParameters mutableCopy]; + + for (NSURLQueryItem *queryItem in urlComponents.queryItems) { + NSString *expectedValue = notEncodedParameters[queryItem.name]; + XCTAssertNotNil(expectedValue, @"Extra parameter encoded: %@ = %@", queryItem.name, + queryItem.value); + + XCTAssertEqualObjects(queryItem.value, expectedValue); + [notEncodedParameters removeObjectForKey:queryItem.name]; + } + + XCTAssertEqual(notEncodedParameters.count, 0, @"The parameters must have been encoded: %@", + notEncodedParameters); +} + - (void)test_multipleRequestsToRetrievePendingDeepLinkShouldNotCrash { id mockService = OCMPartialMock(self.service); [[mockService expect] handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1 @@ -1029,19 +1151,26 @@ - (void)testValidCustomDomainNames { NSArray *urlStrings = @[ @"https://google.com/mylink", // Short FDL starting with 'https://google.com' @"https://google.com/one", // Short FDL starting with 'https://google.com' - @"https://google.com/?link=abcd", // Long FDL starting with 'https://google.com' - @"https://google.com/one/mylink", // Long FDL starting with 'https://google.com/one' + @"https://google.com/one/mylink", // Short FDL starting with 'https://google.com/one' @"https://a.firebase.com/mypath/mylink", // Short FDL starting https://a.firebase.com/mypath + ]; + + NSArray *longFDLURLStrings = @[ @"https://a.firebase.com/mypath/?link=abcd&test=1", // Long FDL starting with // https://a.firebase.com/mypath + @"https://google.com/?link=abcd", // Long FDL starting with 'https://google.com' ]; - for (NSString *urlString in urlStrings) { NSURL *url = [NSURL URLWithString:urlString]; BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url]; - XCTAssertTrue(matchesShortLinkFormat, - @"Non-DDL domain URL matched short link format with URL: %@", url); + XCTAssertTrue(matchesShortLinkFormat, @"URL did not validate as short link: %@", url); + } + for (NSString *urlString in longFDLURLStrings) { + NSURL *url = [NSURL URLWithString:urlString]; + BOOL matchesLongLinkFormat = [self.service canParseUniversalLinkURL:url]; + + XCTAssertTrue(matchesLongLinkFormat, @"URL did not validate as long link: %@", url); } } diff --git a/Example/Firebase.xcodeproj/project.pbxproj b/Example/Firebase.xcodeproj/project.pbxproj index be4844b799f..d4f2342c782 100644 --- a/Example/Firebase.xcodeproj/project.pbxproj +++ b/Example/Firebase.xcodeproj/project.pbxproj @@ -137,7 +137,6 @@ 511DD29D2225C8C40094D78D /* FIRMessagingLinkHandlingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315CC1E8738B70083EDBF /* FIRMessagingLinkHandlingTest.m */; }; 511DD29E2225C8C40094D78D /* FIRMessagingPendingTopicsListTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315CD1E8738B70083EDBF /* FIRMessagingPendingTopicsListTest.m */; }; 511DD29F2225C8C40094D78D /* FIRMessagingPubSubTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315CE1E8738B70083EDBF /* FIRMessagingPubSubTest.m */; }; - 511DD2A02225C8C40094D78D /* FIRMessagingReceiverTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF61BFC216E8B1000A738D4 /* FIRMessagingReceiverTest.m */; }; 511DD2A12225C8C40094D78D /* FIRMessagingRegistrarTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315CF1E8738B70083EDBF /* FIRMessagingRegistrarTest.m */; }; 511DD2A22225C8C40094D78D /* FIRMessagingRemoteNotificationsProxyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315D01E8738B70083EDBF /* FIRMessagingRemoteNotificationsProxyTest.m */; }; 511DD2A32225C8C40094D78D /* FIRMessagingRmqManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315D11E8738B70083EDBF /* FIRMessagingRmqManagerTest.m */; }; @@ -149,6 +148,8 @@ 511DD2A92225C8C40094D78D /* FIRMessagingAnalyticsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE37C63A2163D5F30025D03E /* FIRMessagingAnalyticsTest.m */; }; 511DD2AA2225C8D50094D78D /* FIRMessagingTestUtilities.h in Sources */ = {isa = PBXBuildFile; fileRef = EDF5242A21EA364600BB24C6 /* FIRMessagingTestUtilities.h */; }; 511DD2AB2225C8D50094D78D /* FIRMessagingTestUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = EDF5242B21EA364600BB24C6 /* FIRMessagingTestUtilities.m */; }; + 51284D16224ABD6A00274321 /* FIRMessagingExtensionHelperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 51284D15224ABD6A00274321 /* FIRMessagingExtensionHelperTest.m */; }; + 51284D17224ABE1E00274321 /* FIRMessagingExtensionHelperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 51284D15224ABD6A00274321 /* FIRMessagingExtensionHelperTest.m */; }; 518854D92230652900CA4141 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 518854D82230652900CA4141 /* AppDelegate.m */; }; 518854DC2230652900CA4141 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 518854DB2230652900CA4141 /* ViewController.m */; }; 518854DF2230652900CA4141 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 518854DD2230652900CA4141 /* Main.storyboard */; }; @@ -343,7 +344,6 @@ DE0E5BBB1EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BB91EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m */; }; DE0E5BBC1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BBA1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m */; }; DE0E5BBD1EA7D93100FAA825 /* FIRAuthAppCredentialTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */; }; - DE0E5BBE1EA7D93500FAA825 /* FIRAuthAppDelegateProxyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BB61EA7D91C00FAA825 /* FIRAuthAppDelegateProxyTests.m */; }; DE17A2BA214215C0002A15ED /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; }; DE17A2BB214215C0002A15ED /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; DE17A2BC214215C0002A15ED /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; @@ -633,7 +633,6 @@ DEE14D931E84468D006FA992 /* FIROptionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D7A1E844677006FA992 /* FIROptionsTest.m */; }; DEE14D941E84468D006FA992 /* FIRTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE14D7C1E844677006FA992 /* FIRTestCase.m */; }; DEF288411F9AB6E100D480CF /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DEF288401F9AB6E100D480CF /* Default-568h@2x.png */; }; - DEF61BFD216E8B1100A738D4 /* FIRMessagingReceiverTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF61BFC216E8B1000A738D4 /* FIRMessagingReceiverTest.m */; }; DEF6C30D1FBCE72F005D0740 /* FIRAuthDispatcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9314FF1E86C6FF0083EDBF /* FIRAuthDispatcherTests.m */; }; DEF6C30F1FBCE775005D0740 /* FIRAdditionalUserInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9314FA1E86C6FF0083EDBF /* FIRAdditionalUserInfoTests.m */; }; DEF6C3101FBCE775005D0740 /* FIRApp+FIRAuthUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9314FC1E86C6FF0083EDBF /* FIRApp+FIRAuthUnitTests.m */; }; @@ -1068,6 +1067,7 @@ 511DD27F2225C4D20094D78D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 511DD2882225C5A80094D78D /* Messaging_Tests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Messaging_Tests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 511DD2AC2226005D0094D78D /* FirebaseMessaging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FirebaseMessaging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 51284D15224ABD6A00274321 /* FIRMessagingExtensionHelperTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRMessagingExtensionHelperTest.m; sourceTree = ""; }; 518854D52230652900CA4141 /* InstanceID_Example_tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InstanceID_Example_tvOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 518854D72230652900CA4141 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 518854D82230652900CA4141 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -1135,7 +1135,6 @@ D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRGetProjectConfigResponseTests.m; sourceTree = ""; }; D92C82C61F578DF000D5EAFF /* FIRGetProjectConfigRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRGetProjectConfigRequestTests.m; sourceTree = ""; }; DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthAppCredentialTests.m; sourceTree = ""; }; - DE0E5BB61EA7D91C00FAA825 /* FIRAuthAppDelegateProxyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthAppDelegateProxyTests.m; sourceTree = ""; }; DE0E5BB91EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRVerifyClientRequestTest.m; sourceTree = ""; }; DE0E5BBA1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRVerifyClientResponseTests.m; sourceTree = ""; }; DE17A2A02141FA61002A15ED /* DL-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "DL-Info.plist"; sourceTree = ""; }; @@ -1475,7 +1474,6 @@ DEE14D7C1E844677006FA992 /* FIRTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTestCase.m; sourceTree = ""; }; DEE14D7D1E844677006FA992 /* Tests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; DEF288401F9AB6E100D480CF /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; - DEF61BFC216E8B1000A738D4 /* FIRMessagingReceiverTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRMessagingReceiverTest.m; sourceTree = ""; }; E2C2834C90DBAB56D568189F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRComponentContainerTest.m; sourceTree = ""; }; ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTestComponents.m; sourceTree = ""; }; @@ -2467,7 +2465,6 @@ DE750DB61EB3DD4000A75E47 /* FIRAuthAPNSTokenTests.m */, DE750DB71EB3DD4000A75E47 /* FIRAuthAppCredentialManagerTests.m */, DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */, - DE0E5BB61EA7D91C00FAA825 /* FIRAuthAppDelegateProxyTests.m */, DE9314FD1E86C6FF0083EDBF /* FIRAuthBackendCreateAuthURITests.m */, DE9314FE1E86C6FF0083EDBF /* FIRAuthBackendRPCImplementationTests.m */, DE9314FF1E86C6FF0083EDBF /* FIRAuthDispatcherTests.m */, @@ -2539,6 +2536,7 @@ DE9315C21E8738B70083EDBF /* Tests */ = { isa = PBXGroup; children = ( + 51284D15224ABD6A00274321 /* FIRMessagingExtensionHelperTest.m */, DE8DB550221F5B470068BB0E /* FIRInstanceIDWithFCMTest.m */, DE9315C81E8738B70083EDBF /* FIRMessagingFakeConnection.h */, DE9315CA1E8738B70083EDBF /* FIRMessagingFakeSocket.h */, @@ -2553,7 +2551,6 @@ DE9315CC1E8738B70083EDBF /* FIRMessagingLinkHandlingTest.m */, DE9315CD1E8738B70083EDBF /* FIRMessagingPendingTopicsListTest.m */, DE9315CE1E8738B70083EDBF /* FIRMessagingPubSubTest.m */, - DEF61BFC216E8B1000A738D4 /* FIRMessagingReceiverTest.m */, DE9315CF1E8738B70083EDBF /* FIRMessagingRegistrarTest.m */, DE9315D01E8738B70083EDBF /* FIRMessagingRemoteNotificationsProxyTest.m */, DE9315D11E8738B70083EDBF /* FIRMessagingRmqManagerTest.m */, @@ -3790,6 +3787,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, "es-MX", @@ -4307,13 +4305,13 @@ 511DD2972225C8C40094D78D /* FIRMessagingCodedInputStreamTest.m in Sources */, 511DD2982225C8C40094D78D /* FIRMessagingConnectionTest.m in Sources */, 511DD2992225C8C40094D78D /* FIRMessagingContextManagerServiceTest.m in Sources */, + 51284D17224ABE1E00274321 /* FIRMessagingExtensionHelperTest.m in Sources */, 511DD29A2225C8C40094D78D /* FIRMessagingDataMessageManagerTest.m in Sources */, 511DD29B2225C8C40094D78D /* FIRMessagingFakeConnection.m in Sources */, 511DD29C2225C8C40094D78D /* FIRMessagingFakeSocket.m in Sources */, 511DD29D2225C8C40094D78D /* FIRMessagingLinkHandlingTest.m in Sources */, 511DD29E2225C8C40094D78D /* FIRMessagingPendingTopicsListTest.m in Sources */, 511DD29F2225C8C40094D78D /* FIRMessagingPubSubTest.m in Sources */, - 511DD2A02225C8C40094D78D /* FIRMessagingReceiverTest.m in Sources */, 511DD2A12225C8C40094D78D /* FIRMessagingRegistrarTest.m in Sources */, 511DD2A22225C8C40094D78D /* FIRMessagingRemoteNotificationsProxyTest.m in Sources */, 511DD2A32225C8C40094D78D /* FIRMessagingRmqManagerTest.m in Sources */, @@ -4909,7 +4907,6 @@ 7E9485421F578AC4005A3939 /* FIRAuthURLPresenterTests.m in Sources */, 7E21E0731F857DFC00D0AC1C /* FIROAuthProviderTests.m in Sources */, DE750DBD1EB3DD5B00A75E47 /* FIRAuthAPNSTokenTests.m in Sources */, - DE0E5BBE1EA7D93500FAA825 /* FIRAuthAppDelegateProxyTests.m in Sources */, 7EFA2E041F71C93300DD354F /* FIRUserMetadataTests.m in Sources */, DE0E5BBC1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m in Sources */, DE9315751E86C71C0083EDBF /* FIRUserTests.m in Sources */, @@ -4935,8 +4932,8 @@ EDF5242C21EA37AA00BB24C6 /* FIRMessagingTestUtilities.m in Sources */, DE9316031E8738E60083EDBF /* FIRMessagingSyncMessageManagerTest.m in Sources */, DE9315FF1E8738E60083EDBF /* FIRMessagingRemoteNotificationsProxyTest.m in Sources */, - DEF61BFD216E8B1100A738D4 /* FIRMessagingReceiverTest.m in Sources */, DE9315F81E8738E60083EDBF /* FIRMessagingDataMessageManagerTest.m in Sources */, + 51284D16224ABD6A00274321 /* FIRMessagingExtensionHelperTest.m in Sources */, DE9316051E8738E60083EDBF /* FIRMessagingTestNotificationUtilities.m in Sources */, DE37C63B2163D5F30025D03E /* FIRMessagingAnalyticsTest.m in Sources */, DE9315F61E8738E60083EDBF /* FIRMessagingConnectionTest.m in Sources */, @@ -5970,7 +5967,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -6008,7 +6005,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -6130,9 +6127,7 @@ DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = ( "$(inherited)", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"", + "\"${PODS_ROOT}/../../Firebase/Auth/Source\"/**", ); INFOPLIST_FILE = "Auth/Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -6155,9 +6150,7 @@ DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = ( "$(inherited)", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"", + "\"${PODS_ROOT}/../../Firebase/Auth/Source\"/**", ); INFOPLIST_FILE = "Auth/Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -7301,9 +7294,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = ( "$(inherited)", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"", + "\"${PODS_ROOT}/../../Firebase/Auth/Source\"/**", ); INFOPLIST_FILE = "$(SRCROOT)/Auth/App/iOS/Auth-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -7341,9 +7332,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = ( "$(inherited)", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"", + "\"${PODS_ROOT}/../../Firebase/Auth/Source\"/**", ); INFOPLIST_FILE = "$(SRCROOT)/Auth/App/iOS/Auth-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -7553,9 +7542,7 @@ DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = ( "$(inherited)", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"", + "\"${PODS_ROOT}/../../Firebase/Auth/Source\"/**", "\"${PODS_ROOT}/Headers/Private\"", ); INFOPLIST_FILE = "Auth/Tests/Tests-Info.plist"; @@ -7579,9 +7566,7 @@ DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = ( "$(inherited)", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/RPCs\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source\"", - "\"${PODS_ROOT}/../../Firebase/Auth/Source/AuthProviders\"", + "\"${PODS_ROOT}/../../Firebase/Auth/Source\"/**", "\"${PODS_ROOT}/Headers/Private\"", ); INFOPLIST_FILE = "Auth/Tests/Tests-Info.plist"; @@ -7606,6 +7591,7 @@ "$(inherited)", "COCOAPODS=1", "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "GUL_APP_DELEGATE_TESTING=1", ); HEADER_SEARCH_PATHS = ( "$(inherited)", diff --git a/Example/InstanceID/Tests/FIRInstanceIDAuthKeyChainTest.m b/Example/InstanceID/Tests/FIRInstanceIDAuthKeyChainTest.m index 712ea8f786b..97f575a02d8 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDAuthKeyChainTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDAuthKeyChainTest.m @@ -24,7 +24,6 @@ static NSString *const kFIRInstanceIDTestKeychainId = @"com.google.iid-tests"; static NSString *const kFakeCheckinPlistName = @"com.google.test.IIDStoreTestCheckin"; -static NSString *const kApplicationSupportSubDirectoryName = @"FirebaseInstanceIDCheckinTest"; static NSString *const kAuthorizedEntity = @"test-audience"; static NSString *const kScope = @"test-scope"; diff --git a/Example/InstanceID/Tests/FIRInstanceIDBackupExcludedPlistTest.m b/Example/InstanceID/Tests/FIRInstanceIDBackupExcludedPlistTest.m index c42625070ce..dba4746b58b 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDBackupExcludedPlistTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDBackupExcludedPlistTest.m @@ -21,7 +21,7 @@ #import "Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h" #import "Firebase/InstanceID/FIRInstanceIDStore.h" -static NSString *const kApplicationSupportSubDirectoryName = @"FirebaseInstanceIDBackupPlistTest"; +static NSString *const kSubDirectoryName = @"FirebaseInstanceIDBackupPlistTest"; static NSString *const kTestPlistFileName = @"com.google.test.IIDBackupExcludedPlist"; @interface FIRInstanceIDBackupExcludedPlist () @@ -38,16 +38,14 @@ @implementation FIRInstanceIDBackupExcludedPlistTest - (void)setUp { [super setUp]; - [FIRInstanceIDStore createApplicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; - self.plist = [[FIRInstanceIDBackupExcludedPlist alloc] - initWithFileName:kTestPlistFileName - applicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; + [FIRInstanceIDStore createSubDirectory:kSubDirectoryName]; + self.plist = [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:kTestPlistFileName + subDirectory:kSubDirectoryName]; } - (void)tearDown { [self.plist deleteFile:nil]; - [FIRInstanceIDStore removeApplicationSupportSubDirectory:kApplicationSupportSubDirectoryName - error:nil]; + [FIRInstanceIDStore removeSubDirectory:kSubDirectoryName error:nil]; [super tearDown]; } @@ -78,14 +76,14 @@ - (void)testWriteToPlistInApplicationSupportFolder { XCTAssertTrue([self.plist doesFileExist]); XCTAssertEqualObjects(plistContents, [self.plist contentAsDictionary]); - XCTAssertTrue([self isPlistInApplicationSupportDirectory]); + XCTAssertTrue([self doesPlistFileExist]); } - (void)testMovePlistToApplicationSupportDirectorySuccess { NSDictionary *plistContents = @{@"hello" : @"world", @"id" : @123}; [self.plist writeDictionary:plistContents error:nil]; - [self.plist moveToApplicationSupportSubDirectory]; - XCTAssertTrue([self isPlistInApplicationSupportDirectory]); + [self.plist moveToApplicationSupportSubDirectory:kSubDirectoryName]; + XCTAssertTrue([self doesPlistFileExist]); XCTAssertFalse([self isPlistInDocumentsDirectory]); NSDictionary *newPlistContents = @{@"world" : @"hello"}; @@ -94,42 +92,48 @@ - (void)testMovePlistToApplicationSupportDirectorySuccess { } - (void)testMovePlistToApplicationSupportDirectoryFailure { + // This is to test moving data from deprecated document folder to application folder + // which should only apply to iOS. +#if TARGET_OS_IOS // Delete the subdirectory - [FIRInstanceIDStore removeApplicationSupportSubDirectory:kApplicationSupportSubDirectoryName - error:nil]; + [FIRInstanceIDStore removeSubDirectory:kSubDirectoryName error:nil]; // Create a new plistl This would try to move or write to the ApplicationSupport directory // but since the subdirectory is not there anymore it will fail and rather write to the // Documents folder. - self.plist = [[FIRInstanceIDBackupExcludedPlist alloc] - initWithFileName:kTestPlistFileName - applicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; + self.plist = [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:kTestPlistFileName + subDirectory:kSubDirectoryName]; NSDictionary *plistContents = @{@"hello" : @"world", @"id" : @123}; [self.plist writeDictionary:plistContents error:nil]; - XCTAssertFalse([self isPlistInApplicationSupportDirectory]); + XCTAssertFalse([self doesPlistFileExist]); XCTAssertTrue([self isPlistInDocumentsDirectory]); NSDictionary *newPlistContents = @{@"world" : @"hello"}; [self.plist writeDictionary:newPlistContents error:nil]; + XCTAssertEqualObjects(newPlistContents, [self.plist contentAsDictionary]); // The new file should still be written to the Documents folder. - XCTAssertFalse([self isPlistInApplicationSupportDirectory]); + XCTAssertFalse([self doesPlistFileExist]); XCTAssertTrue([self isPlistInDocumentsDirectory]); +#endif } #pragma mark - Private Helpers -- (BOOL)isPlistInApplicationSupportDirectory { +- (BOOL)doesPlistFileExist { +#if TARGET_OS_TV + NSArray *directoryPaths = + NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); +#else NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); - NSString *applicationSupportDirPath = directoryPaths.lastObject; - NSArray *components = @[ - applicationSupportDirPath, kApplicationSupportSubDirectoryName, - [NSString stringWithFormat:@"%@.plist", kTestPlistFileName] - ]; +#endif + NSString *dirPath = directoryPaths.lastObject; + NSArray *components = + @[ dirPath, kSubDirectoryName, [NSString stringWithFormat:@"%@.plist", kTestPlistFileName] ]; NSString *plistPath = [NSString pathWithComponents:components]; return [[NSFileManager defaultManager] fileExistsAtPath:plistPath]; } @@ -137,9 +141,9 @@ - (BOOL)isPlistInApplicationSupportDirectory { - (BOOL)isPlistInDocumentsDirectory { NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *applicationSupportDirPath = directoryPaths.lastObject; + NSString *documentsSupportDirPath = directoryPaths.lastObject; NSArray *components = - @[ applicationSupportDirPath, [NSString stringWithFormat:@"%@.plist", kTestPlistFileName] ]; + @[ documentsSupportDirPath, [NSString stringWithFormat:@"%@.plist", kTestPlistFileName] ]; NSString *plistPath = [NSString pathWithComponents:components]; return [[NSFileManager defaultManager] fileExistsAtPath:plistPath]; } diff --git a/Example/InstanceID/Tests/FIRInstanceIDCheckinServiceTest.m b/Example/InstanceID/Tests/FIRInstanceIDCheckinServiceTest.m index b595fca0e91..608ac5de461 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDCheckinServiceTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDCheckinServiceTest.m @@ -38,15 +38,15 @@ @implementation FIRInstanceIDCheckinServiceTest - (void)setUp { [super setUp]; + self.checkinService = [[FIRInstanceIDCheckinService alloc] init]; } - (void)tearDown { + self.checkinService = nil; [super tearDown]; } - (void)testCheckinWithSuccessfulCompletion { - self.checkinService = [[FIRInstanceIDCheckinService alloc] init]; - FIRInstanceIDCheckinPreferences *existingCheckin = [self stubCheckinCacheWithValidData]; [FIRInstanceIDCheckinService setCheckinTestBlock:[self successfulCheckinCompletionHandler]]; @@ -79,8 +79,6 @@ - (void)testCheckinWithSuccessfulCompletion { } - (void)testFailedCheckinService { - self.checkinService = [[FIRInstanceIDCheckinService alloc] init]; - [FIRInstanceIDCheckinService setCheckinTestBlock:[self failCheckinCompletionHandler]]; XCTestExpectation *checkinCompletionExpectation = @@ -104,6 +102,29 @@ - (void)testFailedCheckinService { }]; } +- (void)testCheckinServiceFailsWithErrorAfterStopFetching { + [self.checkinService stopFetching]; + + XCTestExpectation *checkinCompletionExpectation = + [self expectationWithDescription:@"Checkin Completion"]; + + [self.checkinService + checkinWithExistingCheckin:nil + completion:^(FIRInstanceIDCheckinPreferences *preferences, NSError *error) { + [checkinCompletionExpectation fulfill]; + XCTAssertNil(preferences); + XCTAssertNotNil(error); + XCTAssertEqual(error.code, kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn); + }]; + + [self waitForExpectationsWithTimeout:5 + handler:^(NSError *error) { + if (error) { + XCTFail(@"Checkin Timeout Error: %@", error); + } + }]; +} + #pragma mark - Stub - (FIRInstanceIDCheckinPreferences *)stubCheckinCacheWithValidData { diff --git a/Example/InstanceID/Tests/FIRInstanceIDCheckinStoreTest.m b/Example/InstanceID/Tests/FIRInstanceIDCheckinStoreTest.m index b052299a741..9aea10043c0 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDCheckinStoreTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDCheckinStoreTest.m @@ -37,7 +37,7 @@ - (NSString *)bundleIdentifierForKeychainAccount; // Testing constants static NSString *const kFakeCheckinPlistName = @"com.google.test.IIDStoreTestCheckin"; -static NSString *const kApplicationSupportSubDirectoryName = @"FirebaseInstanceIDCheckinTest"; +static NSString *const kSubDirectoryName = @"FirebaseInstanceIDCheckinTest"; static NSString *const kAuthorizedEntity = @"test-audience"; static NSString *const kAuthID = @"test-auth-id"; @@ -45,6 +45,8 @@ - (NSString *)bundleIdentifierForKeychainAccount; static NSString *const kScope = @"test-scope"; static NSString *const kSecret = @"test-secret"; static NSString *const kToken = @"test-token"; +static NSString *const kFakeErrorDomain = @"fakeDomain"; +static const NSUInteger kFakeErrorCode = -1; static int64_t const kLastCheckinTimestamp = 123456; @@ -56,7 +58,7 @@ @implementation FIRInstanceIDCheckinStoreTest - (void)setUp { [super setUp]; - [FIRInstanceIDStore createApplicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; + [FIRInstanceIDStore createSubDirectory:kSubDirectoryName]; } - (void)tearDown { @@ -65,8 +67,7 @@ - (void)tearDown { NSError *error; [[NSFileManager defaultManager] removeItemAtPath:path error:&error]; } - [FIRInstanceIDStore removeApplicationSupportSubDirectory:kApplicationSupportSubDirectoryName - error:nil]; + [FIRInstanceIDStore removeSubDirectory:kSubDirectoryName error:nil]; [super tearDown]; } @@ -76,9 +77,9 @@ - (void)tearDown { - (void)testInvalidCheckinPreferencesOnKeychainFail { XCTestExpectation *checkinInvalidExpectation = [self expectationWithDescription:@"Checkin preference should be invalid after keychain failure"]; - FIRInstanceIDBackupExcludedPlist *checkinPlist = [[FIRInstanceIDBackupExcludedPlist alloc] - initWithFileName:kFakeCheckinPlistName - applicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; + FIRInstanceIDBackupExcludedPlist *checkinPlist = + [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:kFakeCheckinPlistName + subDirectory:kSubDirectoryName]; FIRInstanceIDFakeKeychain *fakeKeychain = [[FIRInstanceIDFakeKeychain alloc] init]; @@ -109,10 +110,9 @@ - (void)testInvalidCheckinPreferencesOnKeychainFail { - (void)testCheckinSaveFailsOnKeychainWriteFailure { XCTestExpectation *checkinSaveFailsExpectation = [self expectationWithDescription:@"Checkin save should fail after keychain write failure"]; - FIRInstanceIDBackupExcludedPlist *checkinPlist = [[FIRInstanceIDBackupExcludedPlist alloc] - initWithFileName:kFakeCheckinPlistName - applicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; - + FIRInstanceIDBackupExcludedPlist *checkinPlist = + [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:kFakeCheckinPlistName + subDirectory:kSubDirectoryName]; FIRInstanceIDFakeKeychain *fakeKeychain = [[FIRInstanceIDFakeKeychain alloc] init]; fakeKeychain.cannotWriteToKeychain = YES; @@ -135,14 +135,73 @@ - (void)testCheckinSaveFailsOnKeychainWriteFailure { [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; } +- (void)testCheckinSaveFailsOnPlistWriteFailure { + XCTestExpectation *checkinSaveFailsExpectation = + [self expectationWithDescription:@"Checkin save should fail after plist write failure"]; + FIRInstanceIDBackupExcludedPlist *checkinPlist = + [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:kFakeCheckinPlistName + subDirectory:kSubDirectoryName]; + id plistMock = OCMPartialMock(checkinPlist); + NSError *error = [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:nil]; + OCMStub([plistMock writeDictionary:[OCMArg any] error:[OCMArg setTo:error]]).andReturn(NO); + + FIRInstanceIDFakeKeychain *fakeKeychain = [[FIRInstanceIDFakeKeychain alloc] init]; + + FIRInstanceIDCheckinStore *checkinStore = + [[FIRInstanceIDCheckinStore alloc] initWithCheckinPlist:plistMock keychain:fakeKeychain]; + + __block FIRInstanceIDCheckinPreferences *preferences = + [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:kAuthID secretToken:kSecret]; + [preferences updateWithCheckinPlistContents:[[self class] newCheckinPlistPreferences]]; + [checkinStore saveCheckinPreferences:preferences + handler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.code, kFakeErrorCode); + + preferences = [checkinStore cachedCheckinPreferences]; + XCTAssertNil(preferences.deviceID); + XCTAssertNil(preferences.secretToken); + XCTAssertFalse([preferences hasValidCheckinInfo]); + [checkinSaveFailsExpectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; +} + +- (void)testCheckinSaveSuccess { + XCTestExpectation *checkinSaveSuccessExpectation = + [self expectationWithDescription:@"Checkin save should succeed"]; + FIRInstanceIDBackupExcludedPlist *checkinPlist = + [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:kFakeCheckinPlistName + subDirectory:kSubDirectoryName]; + id plistMock = OCMPartialMock(checkinPlist); + + FIRInstanceIDFakeKeychain *fakeKeychain = [[FIRInstanceIDFakeKeychain alloc] init]; + FIRInstanceIDCheckinStore *checkinStore = + [[FIRInstanceIDCheckinStore alloc] initWithCheckinPlist:plistMock keychain:fakeKeychain]; + + __block FIRInstanceIDCheckinPreferences *preferences = + [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:kAuthID secretToken:kSecret]; + [preferences updateWithCheckinPlistContents:[[self class] newCheckinPlistPreferences]]; + [checkinStore saveCheckinPreferences:preferences + handler:^(NSError *error) { + XCTAssertNil(error); + + preferences = [checkinStore cachedCheckinPreferences]; + XCTAssertEqualObjects(preferences.deviceID, kAuthID); + XCTAssertEqualObjects(preferences.secretToken, kSecret); + [checkinSaveSuccessExpectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; +} + // Write fake checkin data to legacy location, then test if migration worked. - (void)testCheckinMigrationMovesToNewLocationInKeychain { XCTestExpectation *checkinMigrationExpectation = [self expectationWithDescription:@"checkin migration should move to the new location"]; // Create checkin store class. - FIRInstanceIDBackupExcludedPlist *checkinPlist = [[FIRInstanceIDBackupExcludedPlist alloc] - initWithFileName:kFakeCheckinPlistName - applicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; + FIRInstanceIDBackupExcludedPlist *checkinPlist = + [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:kFakeCheckinPlistName + subDirectory:kSubDirectoryName]; FIRInstanceIDFakeKeychain *fakeKeychain = [[FIRInstanceIDFakeKeychain alloc] init]; FIRInstanceIDFakeKeychain *weakKeychain = fakeKeychain; diff --git a/Example/InstanceID/Tests/FIRInstanceIDKeyPairStoreTest.m b/Example/InstanceID/Tests/FIRInstanceIDKeyPairStoreTest.m index a8d98d549c2..ad5f6913875 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDKeyPairStoreTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDKeyPairStoreTest.m @@ -20,6 +20,7 @@ #import "Firebase/InstanceID/FIRInstanceIDConstants.h" #import "Firebase/InstanceID/FIRInstanceIDKeyPair.h" #import "Firebase/InstanceID/FIRInstanceIDKeychain.h" +#import "Firebase/InstanceID/FIRInstanceIDStore.h" #import #import "Firebase/InstanceID/FIRInstanceIDKeyPair.h" @@ -53,6 +54,10 @@ - (void)setUp { [super setUp]; id mockStoreClass = OCMClassMock([FIRInstanceIDKeyPairStore class]); [[[mockStoreClass stub] andReturn:@"com.google.iid-keypairmanager-test"] keyStoreFileName]; + // Should make sure the standard directory is created. + if (![FIRInstanceIDStore hasSubDirectory:kFIRInstanceIDSubDirectoryName]) { + [FIRInstanceIDStore createSubDirectory:kFIRInstanceIDSubDirectoryName]; + } _keyPairStore = [[FIRInstanceIDKeyPairStore alloc] init]; } diff --git a/Example/InstanceID/Tests/FIRInstanceIDResultTest.m b/Example/InstanceID/Tests/FIRInstanceIDResultTest.m index 64e7ff84e07..65cbd7ceec7 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDResultTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDResultTest.m @@ -16,8 +16,9 @@ #import +#import #import -#import "Firebase/InstanceID/FIRInstanceID+Testing.h" + #import "Firebase/InstanceID/NSError+FIRInstanceID.h" static NSString *const kFakeIID = @"fE1e1PZJFSQ"; @@ -29,6 +30,8 @@ @interface FIRInstanceID (ExposedForTest) - (NSString *)cachedTokenIfAvailable; - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler; +- (instancetype)initPrivately; +- (void)start; @end @interface FIRInstanceIDResultTest : XCTestCase { diff --git a/Example/InstanceID/Tests/FIRInstanceIDStoreTest.m b/Example/InstanceID/Tests/FIRInstanceIDStoreTest.m index d414c48258b..efa5edc2f39 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDStoreTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDStoreTest.m @@ -28,7 +28,7 @@ #import "Firebase/InstanceID/FIRInstanceIDTokenStore.h" #import "Firebase/InstanceID/FIRInstanceIDUtilities.h" -static NSString *const kApplicationSupportSubDirectoryName = @"FirebaseInstanceIDStoreTest"; +static NSString *const kSubDirectoryName = @"FirebaseInstanceIDStoreTest"; static NSString *const kAuthorizedEntity = @"test-audience"; static NSString *const kScope = @"test-scope"; @@ -74,12 +74,11 @@ @implementation FIRInstanceIDStoreTest - (void)setUp { [super setUp]; - [FIRInstanceIDStore createApplicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; + [FIRInstanceIDStore createSubDirectory:kSubDirectoryName]; NSString *checkinPlistName = @"com.google.test.IIDStoreTestCheckin"; - self.checkinPlist = [[FIRInstanceIDBackupExcludedPlist alloc] - initWithFileName:checkinPlistName - applicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; + self.checkinPlist = [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:checkinPlistName + subDirectory:kSubDirectoryName]; // checkin store FIRInstanceIDFakeKeychain *fakeKeychain = [[FIRInstanceIDFakeKeychain alloc] init]; @@ -103,8 +102,7 @@ - (void)setUp { - (void)tearDown { [self.instanceIDStore removeAllCachedTokensWithHandler:nil]; [self.instanceIDStore removeCheckinPreferencesWithHandler:nil]; - [FIRInstanceIDStore removeApplicationSupportSubDirectory:kApplicationSupportSubDirectoryName - error:nil]; + [FIRInstanceIDStore removeSubDirectory:kSubDirectoryName error:nil]; [_mockCheckinStore stopMocking]; [_mockTokenStore stopMocking]; [_mockInstanceIDStore stopMocking]; diff --git a/Example/InstanceID/Tests/FIRInstanceIDTest.m b/Example/InstanceID/Tests/FIRInstanceIDTest.m index c3a857f14a4..ebb072e0d5a 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDTest.m @@ -18,13 +18,14 @@ #import #import +#import #import -#import "Firebase/InstanceID/FIRInstanceID+Testing.h" #import "Firebase/InstanceID/FIRInstanceIDAuthService.h" #import "Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h" #import "Firebase/InstanceID/FIRInstanceIDConstants.h" #import "Firebase/InstanceID/FIRInstanceIDKeyPair.h" +#import "Firebase/InstanceID/FIRInstanceIDKeyPairStore.h" #import "Firebase/InstanceID/FIRInstanceIDTokenInfo.h" #import "Firebase/InstanceID/FIRInstanceIDTokenManager.h" #import "Firebase/InstanceID/FIRInstanceIDUtilities.h" @@ -46,12 +47,24 @@ static NSString *const kGoogleAppID = @"1:123:ios:123abc"; @interface FIRInstanceID (ExposedForTest) + +@property(nonatomic, readwrite, strong) FIRInstanceIDTokenManager *tokenManager; +@property(nonatomic, readwrite, strong) FIRInstanceIDKeyPairStore *keyPairStore; +@property(nonatomic, readwrite, copy) NSString *fcmSenderID; + - (NSInteger)retryIntervalToFetchDefaultToken; - (BOOL)isFCMAutoInitEnabled; - (void)didCompleteConfigure; - (NSString *)cachedTokenIfAvailable; - (void)deleteIdentityWithHandler:(FIRInstanceIDDeleteHandler)handler; + (FIRInstanceID *)instanceIDForTests; +- (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler; +- (instancetype)initPrivately; +- (void)start; ++ (int64_t)maxRetryCountForDefaultToken; ++ (int64_t)minIntervalForDefaultTokenRetry; ++ (int64_t)maxRetryIntervalForDefaultTokenInSeconds; + @end @interface FIRInstanceIDTest : XCTestCase @@ -157,7 +170,7 @@ - (void)testTokenShouldBeRefreshedIfCacheTokenNeedsToBeRefreshed { handler:[OCMArg any]]; [self.mockInstanceID didCompleteConfigure]; - OCMVerify([self.mockInstanceID fetchDefaultToken]); + OCMVerify([self.mockInstanceID defaultTokenWithHandler:nil]); XCTAssertEqualObjects([self.mockInstanceID token], kToken); } @@ -172,7 +185,7 @@ - (void)testTokenShouldBeRefreshedIfNoCacheTokenButAutoInitAllowed { [self.mockInstanceID didCompleteConfigure]; - OCMVerify([self.mockInstanceID fetchDefaultToken]); + OCMVerify([self.mockInstanceID defaultTokenWithHandler:nil]); } - (void)testTokenIsDeletedAlongWithIdentity { @@ -519,11 +532,11 @@ - (void)testDefaultToken_noCachedToken { cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:@"*"]; - OCMExpect([self.mockInstanceID fetchDefaultToken]); + OCMExpect([self.mockInstanceID defaultTokenWithHandler:nil]); NSString *token = [self.mockInstanceID token]; XCTAssertNil(token); [self.mockInstanceID stopMocking]; - OCMVerify([self.mockInstanceID fetchDefaultToken]); + OCMVerify([self.mockInstanceID defaultTokenWithHandler:nil]); } /** @@ -534,8 +547,7 @@ - (void)testDefaultToken_validCachedToken { [[[self.mockTokenManager stub] andReturn:sTokenInfo] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:@"*"]; - - [[self.mockInstanceID reject] fetchDefaultToken]; + [[self.mockInstanceID reject] defaultTokenWithHandler:nil]; NSString *token = [self.mockInstanceID token]; XCTAssertEqualObjects(token, kToken); } @@ -589,10 +601,7 @@ - (void)testDefaultTokenFetch_returnValidToken { cachedTokenInfo = sTokenInfo; notificationPostCount++; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" notificationToken = [[self.instanceID token] copy]; -#pragma clang diagnostic pop [defaultTokenExpectation fulfill]; }]; @@ -669,10 +678,7 @@ - (void)testDefaultTokenFetch_retryFetchToken { cachedTokenInfo = sTokenInfo; notificationPostCount++; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" notificationToken = [[self.instanceID token] copy]; -#pragma clang diagnostic pop [defaultTokenExpectation fulfill]; }]; @@ -740,10 +746,7 @@ - (void)testDefaultToken_multipleInvocations { cachedTokenInfo = sTokenInfo; notificationPostCount++; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" notificationToken = [[self.instanceID token] copy]; -#pragma clang diagnostic pop [defaultTokenExpectation fulfill]; }]; @@ -810,6 +813,182 @@ - (void)testDefaultToken_maxRetries { XCTAssertEqual(newTokenFetchCount, [FIRInstanceID maxRetryCountForDefaultToken]); } +- (void)testInstanceIDWithHandler_WhileRequesting_Success { + [self stubKeyPairStoreToReturnValidKeypair]; + [self mockAuthServiceToAlwaysReturnValidCheckin]; + + // Expect `fetchNewTokenWithAuthorizedEntity` to be called once + XCTestExpectation *fetchNewTokenExpectation = + [self expectationWithDescription:@"fetchNewTokenExpectation"]; + __block FIRInstanceIDTokenHandler tokenHandler; + + [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) { + [invocation getArgument:&tokenHandler atIndex:6]; + [fetchNewTokenExpectation fulfill]; + }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity + scope:kFIRInstanceIDDefaultTokenScope + keyPair:[OCMArg any] + options:[OCMArg any] + handler:[OCMArg any]]; + + // Make 1st call + XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"]; + FIRInstanceIDResultHandler handler1 = + ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) { + [handlerExpectation1 fulfill]; + XCTAssertNotNil(result); + XCTAssertEqual(result.token, kToken); + XCTAssertNil(error); + }; + + [self.mockInstanceID instanceIDWithHandler:handler1]; + + // Make 2nd call + XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"]; + FIRInstanceIDResultHandler handler2 = + ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) { + [handlerExpectation2 fulfill]; + XCTAssertNotNil(result); + XCTAssertEqual(result.token, kToken); + XCTAssertNil(error); + }; + + [self.mockInstanceID instanceIDWithHandler:handler2]; + + // Wait for `fetchNewTokenWithAuthorizedEntity` to be performed + [self waitForExpectations:@[ fetchNewTokenExpectation ] timeout:1 enforceOrder:false]; + // Finish token fetch request + tokenHandler(kToken, nil); + + // Wait for completion handlers for both calls to be performed + [self waitForExpectationsWithTimeout:1 handler:NULL]; +} + +- (void)testInstanceIDWithHandler_WhileRequesting_RetrySuccess { + [self stubKeyPairStoreToReturnValidKeypair]; + [self mockAuthServiceToAlwaysReturnValidCheckin]; + + // Expect `fetchNewTokenWithAuthorizedEntity` to be called twice + XCTestExpectation *fetchNewTokenExpectation1 = + [self expectationWithDescription:@"fetchNewTokenExpectation1"]; + XCTestExpectation *fetchNewTokenExpectation2 = + [self expectationWithDescription:@"fetchNewTokenExpectation2"]; + NSArray *fetchNewTokenExpectations = @[ fetchNewTokenExpectation1, fetchNewTokenExpectation2 ]; + + __block NSInteger fetchNewTokenCallCount = 0; + __block FIRInstanceIDTokenHandler tokenHandler; + + [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) { + [invocation getArgument:&tokenHandler atIndex:6]; + [fetchNewTokenExpectations[fetchNewTokenCallCount] fulfill]; + fetchNewTokenCallCount += 1; + }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity + scope:kFIRInstanceIDDefaultTokenScope + keyPair:[OCMArg any] + options:[OCMArg any] + handler:[OCMArg any]]; + + // Mock Instance ID's retry interval to 0, to vastly speed up this test. + [[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken]; + + // Make 1st call + XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"]; + FIRInstanceIDResultHandler handler1 = + ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) { + [handlerExpectation1 fulfill]; + XCTAssertNotNil(result); + XCTAssertEqual(result.token, kToken); + XCTAssertNil(error); + }; + + [self.mockInstanceID instanceIDWithHandler:handler1]; + + // Make 2nd call + XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"]; + FIRInstanceIDResultHandler handler2 = + ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) { + [handlerExpectation2 fulfill]; + XCTAssertNotNil(result); + XCTAssertEqual(result.token, kToken); + XCTAssertNil(error); + }; + + [self.mockInstanceID instanceIDWithHandler:handler2]; + + // Wait for the 1st `fetchNewTokenWithAuthorizedEntity` to be performed + [self waitForExpectations:@[ fetchNewTokenExpectation1 ] timeout:1 enforceOrder:false]; + // Fail for the 1st time + tokenHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]); + + // Wait for the 2nd token feth + [self waitForExpectations:@[ fetchNewTokenExpectation2 ] timeout:1 enforceOrder:false]; + // Finish with success + tokenHandler(kToken, nil); + + // Wait for completion handlers for both calls to be performed + [self waitForExpectationsWithTimeout:1 handler:NULL]; +} + +- (void)testInstanceIDWithHandler_WhileRequesting_RetryFailure { + [self stubKeyPairStoreToReturnValidKeypair]; + [self mockAuthServiceToAlwaysReturnValidCheckin]; + + // Expect `fetchNewTokenWithAuthorizedEntity` to be called once + NSMutableArray *fetchNewTokenExpectations = [NSMutableArray array]; + for (NSInteger i = 0; i < [[self.instanceID class] maxRetryCountForDefaultToken]; ++i) { + NSString *name = [NSString stringWithFormat:@"fetchNewTokenExpectation-%ld", (long)i]; + [fetchNewTokenExpectations addObject:[self expectationWithDescription:name]]; + } + + __block NSInteger fetchNewTokenCallCount = 0; + __block FIRInstanceIDTokenHandler tokenHandler; + + [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) { + [invocation getArgument:&tokenHandler atIndex:6]; + [fetchNewTokenExpectations[fetchNewTokenCallCount] fulfill]; + fetchNewTokenCallCount += 1; + }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity + scope:kFIRInstanceIDDefaultTokenScope + keyPair:[OCMArg any] + options:[OCMArg any] + handler:[OCMArg any]]; + + // Mock Instance ID's retry interval to 0, to vastly speed up this test. + [[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken]; + + // Make 1st call + XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"]; + FIRInstanceIDResultHandler handler1 = + ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) { + [handlerExpectation1 fulfill]; + XCTAssertNil(result); + XCTAssertNotNil(error); + }; + + [self.mockInstanceID instanceIDWithHandler:handler1]; + + // Make 2nd call + XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"]; + FIRInstanceIDResultHandler handler2 = + ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) { + [handlerExpectation2 fulfill]; + XCTAssertNil(result); + XCTAssertNotNil(error); + }; + + [self.mockInstanceID instanceIDWithHandler:handler2]; + + for (NSInteger i = 0; i < [[self.instanceID class] maxRetryCountForDefaultToken]; ++i) { + // Wait for the i `fetchNewTokenWithAuthorizedEntity` to be performed + [self waitForExpectations:@[ fetchNewTokenExpectations[i] ] timeout:1 enforceOrder:false]; + // Fail for the i time + tokenHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]); + } + + // Wait for completion handlers for both calls to be performed + [self waitForExpectationsWithTimeout:1 handler:NULL]; +} + /** * Tests a Keychain read failure while we try to fetch a new InstanceID token. If the Keychain * read fails we won't be able to fetch the public key which is required while fetching a new diff --git a/Example/InstanceID/Tests/FIRInstanceIDTokenManagerTest.m b/Example/InstanceID/Tests/FIRInstanceIDTokenManagerTest.m index 8f52a83ab76..96448387d76 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDTokenManagerTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDTokenManagerTest.m @@ -30,7 +30,7 @@ #import "Firebase/InstanceID/FIRInstanceIDTokenOperation.h" #import "Firebase/InstanceID/FIRInstanceIDTokenStore.h" -static NSString *const kApplicationSupportSubDirectoryName = @"FirebaseInstanceIDTokenManagerTest"; +static NSString *const kSubDirectoryName = @"FirebaseInstanceIDTokenManagerTest"; static NSString *const kAuthorizedEntity = @"test-authorized-entity"; static NSString *const kScope = @"test-scope"; @@ -91,12 +91,12 @@ @implementation FIRInstanceIDTokenManagerTest - (void)setUp { [super setUp]; - [FIRInstanceIDStore createApplicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; + [FIRInstanceIDStore createSubDirectory:kSubDirectoryName]; NSString *checkinPlistFilename = @"com.google.test.IIDCheckinTest"; - self.checkinPlist = [[FIRInstanceIDBackupExcludedPlist alloc] - initWithFileName:checkinPlistFilename - applicationSupportSubDirectory:kApplicationSupportSubDirectoryName]; + self.checkinPlist = + [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:checkinPlistFilename + subDirectory:kSubDirectoryName]; // checkin store FIRInstanceIDFakeKeychain *fakeCheckinKeychain = [[FIRInstanceIDFakeKeychain alloc] init]; @@ -123,8 +123,7 @@ - (void)tearDown { } self.tokenManager = nil; - [FIRInstanceIDStore removeApplicationSupportSubDirectory:kApplicationSupportSubDirectoryName - error:nil]; + [FIRInstanceIDStore removeSubDirectory:kSubDirectoryName error:nil]; [super tearDown]; } diff --git a/Example/InstanceID/Tests/FIRInstanceIDTokenOperationsTest.m b/Example/InstanceID/Tests/FIRInstanceIDTokenOperationsTest.m index 135f8cb2085..dd7cfd7cb99 100644 --- a/Example/InstanceID/Tests/FIRInstanceIDTokenOperationsTest.m +++ b/Example/InstanceID/Tests/FIRInstanceIDTokenOperationsTest.m @@ -32,6 +32,8 @@ #import "Firebase/InstanceID/NSError+FIRInstanceID.h" #import "Firebase/InstanceID/Public/FIRInstanceID.h" +#import + static NSString *kDeviceID = @"fakeDeviceID"; static NSString *kSecretToken = @"fakeSecretToken"; static NSString *kDigestString = @"test-digest"; @@ -317,6 +319,46 @@ - (void)testHTTPAuthHeaderGenerationFromCheckin { XCTAssertEqualObjects(generatedHeader, expectedHeader); } +- (void)testTokenFetchOperationFirebaseUserAgentHeader { + XCTestExpectation *completionExpectation = + [self expectationWithDescription:@"completionExpectation"]; + + FIRInstanceIDCheckinPreferences *checkinPreferences = + [self setCheckinPreferencesWithLastCheckinTime:0]; + + FIRInstanceIDTokenFetchOperation *operation = + [[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity + scope:kScope + options:nil + checkinPreferences:checkinPreferences + keyPair:self.keyPair]; + operation.testBlock = + ^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) { + NSString *userAgentValue = request.allHTTPHeaderFields[kFIRInstanceIDFirebaseUserAgentKey]; + XCTAssertEqualObjects(userAgentValue, [FIRApp firebaseUserAgent]); + + // Return a response with Error=RST + NSData *responseBody = [self dataForFetchRequest:request returnValidToken:NO]; + NSHTTPURLResponse *responseObject = [[NSHTTPURLResponse alloc] initWithURL:request.URL + statusCode:200 + HTTPVersion:@"HTTP/1.1" + headerFields:nil]; + response(responseBody, responseObject, nil); + }; + + [operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result, + NSString *_Nullable token, NSError *_Nullable error) { + [completionExpectation fulfill]; + }]; + + [operation start]; + + [self waitForExpectationsWithTimeout:0.25 + handler:^(NSError *_Nullable error) { + XCTAssertNil(error.localizedDescription); + }]; +} + #pragma mark - Internal Helpers - (NSData *)dataForFetchRequest:(NSURLRequest *)request returnValidToken:(BOOL)returnValidToken { NSString *response; diff --git a/Example/Messaging/Tests/FIRInstanceIDWithFCMTest.m b/Example/Messaging/Tests/FIRInstanceIDWithFCMTest.m index afaabbcc38c..8f042a66463 100644 --- a/Example/Messaging/Tests/FIRInstanceIDWithFCMTest.m +++ b/Example/Messaging/Tests/FIRInstanceIDWithFCMTest.m @@ -17,7 +17,7 @@ #import #import -#import +#import #import #import "FIRMessaging_Private.h" #import "FIRMessaging.h" diff --git a/Example/Messaging/Tests/FIRMessagingDataMessageManagerTest.m b/Example/Messaging/Tests/FIRMessagingDataMessageManagerTest.m index 8aaad512ebe..6f6b9f70c09 100644 --- a/Example/Messaging/Tests/FIRMessagingDataMessageManagerTest.m +++ b/Example/Messaging/Tests/FIRMessagingDataMessageManagerTest.m @@ -108,7 +108,6 @@ - (void)testSendValidMessage_withNoConnection { error:[OCMArg anyObjectRef]]; // should be logged into the service - [self addFakeFIRMessagingRegistrationToken]; [self.dataMessageManager setDeviceAuthID:@"auth-id" secretToken:@"secret-token"]; // try to send messages with no connection should be queued into RMQ NSMutableDictionary *message = [self upstreamMessageWithID:messageID ttl:-1 delay:0]; @@ -158,7 +157,6 @@ - (void)testSendInvalidMessage_withNoTo { }]]); // should be logged into the service - [self addFakeFIRMessagingRegistrationToken]; [self.dataMessageManager setDeviceAuthID:@"auth-id" secretToken:@"secret-token"]; [self.dataMessageManager sendDataMessageStanza:message]; @@ -187,7 +185,6 @@ - (void)testSendInvalidMessage_withSizeExceeded { return NO; }]]); - [self addFakeFIRMessagingRegistrationToken]; // should be logged into the service [self.dataMessageManager setDeviceAuthID:@"auth-id" secretToken:@"secret-token"]; [self.dataMessageManager sendDataMessageStanza:message]; @@ -215,7 +212,6 @@ - (void)testSendValidMessage_withRmqSaveError { // should be logged into the service [self.dataMessageManager setDeviceAuthID:@"auth-id" secretToken:@"secret-token"]; - [self addFakeFIRMessagingRegistrationToken]; [self.dataMessageManager sendDataMessageStanza:message]; OCMVerifyAll(self.mockReceiver); @@ -240,7 +236,6 @@ - (void)testSendValidMessage_withTTL0 { OCMExpect([self.mockClient sendMessage:[OCMArg checkWithBlock:isValidStanza]]); [self.dataMessageManager setDeviceAuthID:@"auth-id" secretToken:@"secret-token"]; - [self addFakeFIRMessagingRegistrationToken]; [self.dataMessageManager sendDataMessageStanza:message]; OCMVerifyAll(self.mockClient); @@ -276,7 +271,6 @@ - (void)XXX_testSendValidMessage_withTTL0AndNoFIRMessagingConnection { OCMExpect([self.mockClient retryConnectionImmediately:[OCMArg isEqual:@YES]]); [self.dataMessageManager setDeviceAuthID:@"auth-id" secretToken:@"secret-token"]; - [self addFakeFIRMessagingRegistrationToken]; [self.dataMessageManager sendDataMessageStanza:message]; OCMVerifyAll(self.mockClient); @@ -302,7 +296,6 @@ - (void)xxx_testSendValidMessage_withTTL0AndNoNetwork { }]]); [self.dataMessageManager setDeviceAuthID:@"auth-id" secretToken:@"secret-token"]; - [self addFakeFIRMessagingRegistrationToken]; [self.dataMessageManager sendDataMessageStanza:message]; OCMVerifyAll(self.mockReceiver); @@ -329,7 +322,6 @@ - (void)XXX_testDelayedMessagesBeingResentOnReconnect { error:[OCMArg isNil]]); [self.dataMessageManager setDeviceAuthID:@"auth-id" secretToken:@"secret-token"]; - [self addFakeFIRMessagingRegistrationToken]; [self.dataMessageManager sendDataMessageStanza:message]; __block FIRMessagingDataMessageHandler dataMessageHandler; @@ -496,7 +488,6 @@ - (void)testResendSavedMessages { // have a real RMQ store [self.dataMessageManager setRmq2Manager:newRmqManager]; - [self addFakeFIRMessagingRegistrationToken]; [self.dataMessageManager setDeviceAuthID:@"auth-id" secretToken:@"secret-token"]; // send a couple of message with no connection should be saved to RMQ @@ -576,12 +567,6 @@ - (void)testResendingExpiredMessagesFails { dataMessageHandler:nil]; } -#pragma mark - Private - -- (void)addFakeFIRMessagingRegistrationToken { - // [[FIRMessagingDefaultsManager sharedInstance] saveAppIDToken:kFIRMessagingAppIDToken]; -} - #pragma mark - Create Packet - (GtalkDataMessageStanza *)validDataMessagePacket { diff --git a/Example/Messaging/Tests/FIRMessagingExtensionHelperTest.m b/Example/Messaging/Tests/FIRMessagingExtensionHelperTest.m new file mode 100644 index 00000000000..4bcb76ece53 --- /dev/null +++ b/Example/Messaging/Tests/FIRMessagingExtensionHelperTest.m @@ -0,0 +1,112 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +#import + +#import "FIRMessaging.h" +#import "FIRMessagingExtensionHelper.h" + +typedef void (^FIRMessagingContentHandler)(UNNotificationContent *content); + +static NSString *const kFCMPayloadOptionsName = @"fcm_options"; +static NSString *const kFCMPayloadOptionsImageURLName = @"image"; +static NSString *const kValidImageURL = + @"https://firebasestorage.googleapis.com/v0/b/fcm-ios-f7f9c.appspot.com/o/" + @"chubbyBunny.jpg?alt=media&token=d6c56a57-c007-4b27-b20f-f267cc83e9e5"; + +@interface FIRMessagingExtensionHelper (ExposedForTest) +#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +- (void)loadAttachmentForURL:(NSURL *)attachmentURL + completionHandler:(void (^)(UNNotificationAttachment *))completionHandler; +#endif +@end + +@interface FIRMessagingExtensionHelperTest : XCTestCase { + id _mockExtensionHelper; +} +@end + +@implementation FIRMessagingExtensionHelperTest + +- (void)setUp { + [super setUp]; + FIRMessagingExtensionHelper *extensionHelper = [FIRMessaging extensionHelper]; + _mockExtensionHelper = OCMPartialMock(extensionHelper); +} + +- (void)tearDown { + [_mockExtensionHelper stopMocking]; +} + +#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +#ifdef COCOAPODS +// This test requires internet access. +- (void)testModifyNotificationWithValidPayloadData { + XCTestExpectation *validPayloadExpectation = + [self expectationWithDescription:@"Test payload is valid."]; + + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + content.userInfo = @{kFCMPayloadOptionsName : @{kFCMPayloadOptionsImageURLName : kValidImageURL}}; + FIRMessagingContentHandler handler = ^(UNNotificationContent *content) { + [validPayloadExpectation fulfill]; + }; + [_mockExtensionHelper populateNotificationContent:content withContentHandler:handler]; + + OCMVerify([_mockExtensionHelper loadAttachmentForURL:[OCMArg any] + completionHandler:[OCMArg any]]); + [self waitForExpectationsWithTimeout:1.0 handler:nil]; +} +#endif + +- (void)testModifyNotificationWithInvalidPayloadData { + XCTestExpectation *validPayloadExpectation = + [self expectationWithDescription:@"Test payload is valid."]; + + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + content.userInfo = + @{kFCMPayloadOptionsName : @{kFCMPayloadOptionsImageURLName : @"a invalid URL"}}; + FIRMessagingContentHandler handler = ^(UNNotificationContent *content) { + [validPayloadExpectation fulfill]; + }; + [_mockExtensionHelper populateNotificationContent:content withContentHandler:handler]; + + OCMReject([_mockExtensionHelper loadAttachmentForURL:[OCMArg any] + completionHandler:[OCMArg any]]); + [self waitForExpectationsWithTimeout:1.0 handler:nil]; +} + +- (void)testModifyNotificationWithEmptyPayloadData { + XCTestExpectation *validPayloadExpectation = + [self expectationWithDescription:@"Test payload is valid."]; + + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + content.userInfo = + @{kFCMPayloadOptionsName : @{kFCMPayloadOptionsImageURLName : @"a invalid URL"}}; + FIRMessagingContentHandler handler = ^(UNNotificationContent *content) { + [validPayloadExpectation fulfill]; + }; + [_mockExtensionHelper populateNotificationContent:content withContentHandler:handler]; + + OCMReject([_mockExtensionHelper loadAttachmentForURL:[OCMArg any] + completionHandler:[OCMArg any]]); + [self waitForExpectationsWithTimeout:1.0 handler:nil]; +} +#endif + +@end diff --git a/Example/Messaging/Tests/FIRMessagingReceiverTest.m b/Example/Messaging/Tests/FIRMessagingReceiverTest.m deleted file mode 100644 index 95e6dd9c497..00000000000 --- a/Example/Messaging/Tests/FIRMessagingReceiverTest.m +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import - -#import - -#import "FIRMessaging.h" -#import "FIRMessaging_Private.h" -#import "FIRMessagingTestUtilities.h" - -NSString *const kFIRMessagingTestsReceiverSuiteName = @"com.messaging.test_receiverTest"; - -@interface FIRMessagingReceiverTest : XCTestCase -@property(nonatomic, readonly, strong) FIRMessaging *messaging; - -@end - -@implementation FIRMessagingReceiverTest -- (void)setUp { - [super setUp]; - - NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kFIRMessagingTestsReceiverSuiteName]; - _messaging = [FIRMessagingTestUtilities messagingForTestsWithUserDefaults:defaults]; -} - -- (void)tearDown { - [self.messaging.messagingUserDefaults removePersistentDomainForName:kFIRMessagingTestsReceiverSuiteName]; - _messaging = nil; - - [super tearDown]; -} - -- (void)testUseMessagingDelegate { - XCTAssertFalse(_messaging.useMessagingDelegateForDirectChannel); - - _messaging.useMessagingDelegateForDirectChannel = YES; - XCTAssertTrue(_messaging.useMessagingDelegateForDirectChannel); -} - -- (void)testUseMessagingDelegateFlagOverridedByPlistWithFalseValue { - id bundleMock = OCMPartialMock([NSBundle mainBundle]); - OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistUseMessagingDelegate]) - .andReturn(nil); - XCTAssertFalse(_messaging.useMessagingDelegateForDirectChannel); - - [bundleMock stopMocking]; -} - -- (void)testUseMessagingDelegateFlagOverridedByPlistWithTrueValue { - id bundleMock = OCMPartialMock([NSBundle mainBundle]); - OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistUseMessagingDelegate]) - .andReturn(@YES); - XCTAssertTrue(_messaging.useMessagingDelegateForDirectChannel); - - [bundleMock stopMocking]; -} -@end diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m index 7a91dd68bd3..dae5297fa6a 100644 --- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m +++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m @@ -24,38 +24,34 @@ #import "FIRMessaging.h" #import "FIRMessagingRemoteNotificationsProxy.h" -#pragma mark - Expose Internal Methods for Testing -// Expose some internal properties and methods here, in order to test -@interface FIRMessagingRemoteNotificationsProxy () - -@property(readonly, nonatomic) BOOL didSwizzleMethods; -@property(readonly, nonatomic) BOOL didSwizzleAppDelegateMethods; - -@property(readonly, nonatomic) BOOL hasSwizzledUserNotificationDelegate; -@property(readonly, nonatomic) BOOL isObservingUserNotificationDelegateChanges; - -@property(strong, readonly, nonatomic) id userNotificationCenter; -@property(strong, readonly, nonatomic) id currentUserNotificationCenterDelegate; - -+ (instancetype)sharedProxy; - -- (BOOL)swizzleAppDelegateMethods:(id)appDelegate; -- (void)listenForDelegateChangesInUserNotificationCenter:(id)notificationCenter; -- (void)swizzleUserNotificationCenterDelegate:(id)delegate; -- (void)unswizzleUserNotificationCenterDelegate:(id)delegate; - -void FCM_swizzle_appDidReceiveRemoteNotification(id self, - SEL _cmd, - UIApplication *app, - NSDictionary *userInfo); -void FCM_swizzle_appDidReceiveRemoteNotificationWithHandler( - id self, SEL _cmd, UIApplication *app, NSDictionary *userInfo, - void (^handler)(UIBackgroundFetchResult)); -void FCM_swizzle_willPresentNotificationWithHandler( - id self, SEL _cmd, id center, id notification, void (^handler)(NSUInteger)); -void FCM_swizzle_didReceiveNotificationResponseWithHandler( - id self, SEL _cmd, id center, id response, void (^handler)()); +#import +#pragma mark - Invalid App Delegate or UNNotificationCenter + +@interface RandomObject : NSObject +@property(nonatomic, weak) id delegate; +@end +@implementation RandomObject +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options)) +completionHandler { +} + +#if TARGET_OS_IOS +- (void)userNotificationCenter:(UNUserNotificationCenter *)center +didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void(^)(void))completionHandler { +} +#endif // TARGET_OS_IOS + +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @end #pragma mark - Incomplete App Delegate @@ -68,6 +64,8 @@ @implementation IncompleteAppDelegate @interface FakeAppDelegate : NSObject @property(nonatomic) BOOL remoteNotificationMethodWasCalled; @property(nonatomic) BOOL remoteNotificationWithFetchHandlerWasCalled; +@property(nonatomic, strong) NSData *deviceToken; +@property(nonatomic, strong) NSError *registerForRemoteNotificationsError; @end @implementation FakeAppDelegate #if TARGET_OS_IOS @@ -81,6 +79,17 @@ - (void)application:(UIApplication *)application fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { self.remoteNotificationWithFetchHandlerWasCalled = YES; } + +- (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + self.deviceToken = deviceToken; +} + +- (void)application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + self.registerForRemoteNotificationsError = error; +} + @end #pragma mark - Incompete UNUserNotificationCenterDelegate @@ -104,21 +113,28 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center self.willPresentWasCalled = YES; } #if TARGET_OS_IOS -- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { +- (void)userNotificationCenter:(UNUserNotificationCenter *)center +didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler { self.didReceiveResponseWasCalled = YES; } #endif // TARGET_OS_IOS @end #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +@interface GULAppDelegateSwizzler (FIRMessagingRemoteNotificationsProxyTest) ++ (void)resetProxyOriginalDelegateOnceToken; +@end + #pragma mark - Local, Per-Test Properties @interface FIRMessagingRemoteNotificationsProxyTest : XCTestCase @property(nonatomic, strong) FIRMessagingRemoteNotificationsProxy *proxy; -@property(nonatomic, strong) id mockProxy; @property(nonatomic, strong) id mockProxyClass; -@property(nonatomic, strong) id mockMessagingClass; +@property(nonatomic, strong) id mockSharedApplication; +@property(nonatomic, strong) id mockMessaging; +@property(nonatomic, strong) id mockUserNotificationCenter; @end @@ -126,26 +142,36 @@ @implementation FIRMessagingRemoteNotificationsProxyTest - (void)setUp { [super setUp]; + + [GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken]; + + _mockSharedApplication = OCMPartialMock([UIApplication sharedApplication]); + + _mockMessaging = OCMClassMock([FIRMessaging class]); + OCMStub([_mockMessaging messaging]).andReturn(_mockMessaging); + _proxy = [[FIRMessagingRemoteNotificationsProxy alloc] init]; - _mockProxy = OCMPartialMock(_proxy); _mockProxyClass = OCMClassMock([FIRMessagingRemoteNotificationsProxy class]); - // Update +sharedProxy to always return our partial mock of FIRMessagingRemoteNotificationsProxy - OCMStub([_mockProxyClass sharedProxy]).andReturn(_mockProxy); - // Many of our swizzled methods call [FIRMessaging messaging], but we don't need it, - // so just stub it to return nil - _mockMessagingClass = OCMClassMock([FIRMessaging class]); - OCMStub([_mockMessagingClass messaging]).andReturn(nil); + // Update +sharedProxy to always return our test instance + OCMStub([_mockProxyClass sharedProxy]).andReturn(self.proxy); + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + _mockUserNotificationCenter = OCMPartialMock([UNUserNotificationCenter currentNotificationCenter]); +#endif } - (void)tearDown { - [_mockMessagingClass stopMocking]; - _mockMessagingClass = nil; - [_mockProxyClass stopMocking]; _mockProxyClass = nil; - [_mockProxy stopMocking]; - _mockProxy = nil; + [_mockMessaging stopMocking]; + _mockMessaging = nil; + + [_mockSharedApplication stopMocking]; + _mockSharedApplication = nil; + + [_mockUserNotificationCenter stopMocking]; + _mockUserNotificationCenter = nil; _proxy = nil; [super tearDown]; @@ -154,77 +180,152 @@ - (void)tearDown { #pragma mark - Method Swizzling Tests - (void)testSwizzlingNonAppDelegate { - id randomObject = @"Random Object that is not an App Delegate"; - [self.proxy swizzleAppDelegateMethods:randomObject]; - XCTAssertFalse(self.proxy.didSwizzleAppDelegateMethods); -} + RandomObject *invalidAppDelegate = [[RandomObject alloc] init]; + [OCMStub([self.mockSharedApplication delegate]) andReturn:invalidAppDelegate]; + [self.proxy swizzleMethodsIfPossible]; -- (void)testSwizzlingAppDelegate { - IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; - [self.proxy swizzleAppDelegateMethods:incompleteAppDelegate]; - XCTAssertTrue(self.proxy.didSwizzleAppDelegateMethods); + OCMReject([self.mockMessaging appDidReceiveMessage:[OCMArg any]]); + + [invalidAppDelegate application:self.mockSharedApplication + didReceiveRemoteNotification:@{} + fetchCompletionHandler:^(UIBackgroundFetchResult result) {}]; } - (void)testSwizzledIncompleteAppDelegateRemoteNotificationMethod { - IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; - [self.mockProxy swizzleAppDelegateMethods:incompleteAppDelegate]; +#if TARGET_OS_IOS + IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; + [OCMStub([self.mockSharedApplication delegate]) andReturn:incompleteAppDelegate]; + [self.proxy swizzleMethodsIfPossible]; - [incompleteAppDelegate application:OCMClassMock([UIApplication class]) - didReceiveRemoteNotification:@{}]; - // Verify our swizzled method was called - OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification); + NSDictionary *notification = @{@"test" : @""}; + OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); + + [incompleteAppDelegate application:self.mockSharedApplication + didReceiveRemoteNotification:notification]; + + [self.mockMessaging verify]; +#endif // TARGET_OS_IOS } - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod { IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; - [self.mockProxy swizzleAppDelegateMethods:incompleteAppDelegate]; + [OCMStub([self.mockSharedApplication delegate]) andReturn:incompleteAppDelegate]; + [self.proxy swizzleMethodsIfPossible]; + SEL remoteNotificationWithFetchHandler = - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); XCTAssertFalse([incompleteAppDelegate respondsToSelector:remoteNotificationWithFetchHandler]); + +#if TARGET_OS_IOS SEL remoteNotification = @selector(application:didReceiveRemoteNotification:); XCTAssertTrue([incompleteAppDelegate respondsToSelector:remoteNotification]); +#endif // TARGET_OS_IOS } - (void)testSwizzledAppDelegateRemoteNotificationMethods { #if TARGET_OS_IOS FakeAppDelegate *appDelegate = [[FakeAppDelegate alloc] init]; - [self.mockProxy swizzleAppDelegateMethods:appDelegate]; - [appDelegate application:OCMClassMock([UIApplication class]) didReceiveRemoteNotification:@{}]; + [OCMStub([self.mockSharedApplication delegate]) andReturn:appDelegate]; + [self.proxy swizzleMethodsIfPossible]; + + NSDictionary *notification = @{@"test" : @""}; + + //Test application:didReceiveRemoteNotification: + // Verify our swizzled method was called - OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification); + OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); + + // Call the method + [appDelegate application:self.mockSharedApplication + didReceiveRemoteNotification:notification]; + // Verify our original method was called XCTAssertTrue(appDelegate.remoteNotificationMethodWasCalled); + [self.mockMessaging verify]; + + //Test application:didReceiveRemoteNotification:fetchCompletionHandler: - // Now call the remote notification with handler method - [appDelegate application:OCMClassMock([UIApplication class]) - didReceiveRemoteNotification:@{} - fetchCompletionHandler:^(UIBackgroundFetchResult result) {}]; // Verify our swizzled method was called - OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler); + OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); + + [appDelegate application:self.mockSharedApplication + didReceiveRemoteNotification:notification + fetchCompletionHandler:^(UIBackgroundFetchResult result) {}]; + // Verify our original method was called XCTAssertTrue(appDelegate.remoteNotificationWithFetchHandlerWasCalled); -#endif + [self.mockMessaging verify]; + + // Verify application:didRegisterForRemoteNotificationsWithDeviceToken: + NSData *deviceToken = [NSData data]; + + OCMExpect([self.mockMessaging setAPNSToken:deviceToken]); + + [appDelegate application:self.mockSharedApplication + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + + XCTAssertEqual(appDelegate.deviceToken, deviceToken); + [self.mockMessaging verify]; + + // Verify application:didFailToRegisterForRemoteNotificationsWithError: + NSError *error = [NSError errorWithDomain:@"tests" code:-1 userInfo:nil]; + + [appDelegate application:self.mockSharedApplication + didFailToRegisterForRemoteNotificationsWithError:error]; + + XCTAssertEqual(appDelegate.registerForRemoteNotificationsError, error); + +#endif } #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - (void)testListeningForDelegateChangesOnInvalidUserNotificationCenter { - id randomObject = @"Random Object that is not a User Notification Center"; - [self.proxy listenForDelegateChangesInUserNotificationCenter:randomObject]; - XCTAssertFalse(self.proxy.isObservingUserNotificationDelegateChanges); + RandomObject *invalidNotificationCenter = [[RandomObject alloc] init]; + OCMStub([self.mockUserNotificationCenter currentNotificationCenter]).andReturn(invalidNotificationCenter); + [self.proxy swizzleMethodsIfPossible]; + + OCMReject([self.mockMessaging appDidReceiveMessage:[OCMArg any]]); + + [(id)invalidNotificationCenter.delegate + userNotificationCenter:self.mockUserNotificationCenter + willPresentNotification:OCMClassMock([UNNotification class]) + withCompletionHandler:^(UNNotificationPresentationOptions options) { + }]; } - (void)testSwizzlingInvalidUserNotificationCenterDelegate { - id randomObject = @"Random Object that is not a User Notification Center Delegate"; - [self.proxy swizzleUserNotificationCenterDelegate:randomObject]; - XCTAssertFalse(self.proxy.hasSwizzledUserNotificationDelegate); + RandomObject *invalidDelegate = [[RandomObject alloc] init]; + OCMStub([self.mockUserNotificationCenter delegate]).andReturn(invalidDelegate); + [self.proxy swizzleMethodsIfPossible]; + + OCMReject([self.mockMessaging appDidReceiveMessage:[OCMArg any]]); + + [invalidDelegate + userNotificationCenter:self.mockUserNotificationCenter + willPresentNotification:OCMClassMock([UNNotification class]) + withCompletionHandler:^(UNNotificationPresentationOptions options) { + }]; } - (void)testSwizzlingUserNotificationsCenterDelegate { FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init]; - [self.proxy swizzleUserNotificationCenterDelegate:delegate]; - XCTAssertTrue(self.proxy.hasSwizzledUserNotificationDelegate); + OCMStub([self.mockUserNotificationCenter delegate]).andReturn(delegate); + [self.proxy swizzleMethodsIfPossible]; + + NSDictionary *message = @{@"message": @""}; + id notification = [self userNotificationWithMessage:message]; + + OCMExpect([self.mockMessaging appDidReceiveMessage:message]); + + [delegate + userNotificationCenter:self.mockUserNotificationCenter + willPresentNotification:notification + withCompletionHandler:^(UNNotificationPresentationOptions options) { + }]; + + [self.mockMessaging verify]; } // Use a fake delegate that doesn't actually implement the needed delegate method. @@ -237,7 +338,8 @@ - (void)testIncompleteUserNotificationCenterDelegateMethod { } IncompleteUserNotificationCenterDelegate *delegate = [[IncompleteUserNotificationCenterDelegate alloc] init]; - [self.mockProxy swizzleUserNotificationCenterDelegate:delegate]; + OCMStub([self.mockUserNotificationCenter delegate]).andReturn(delegate); + [self.proxy swizzleMethodsIfPossible]; // Because the incomplete delete does not implement either of the optional delegate methods, we // should swizzle nothing. If we had swizzled them, then respondsToSelector: would return YES // even though the delegate does not implement the methods. @@ -250,55 +352,67 @@ - (void)testIncompleteUserNotificationCenterDelegateMethod { // Use an object that does actually implement the optional methods. Both should be called. - (void)testSwizzledUserNotificationsCenterDelegate { - // Early exit if running on pre iOS 10 - if (![UNNotification class]) { - return; - } FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init]; - [self.mockProxy swizzleUserNotificationCenterDelegate:delegate]; + OCMStub([self.mockUserNotificationCenter delegate]).andReturn(delegate); + [self.proxy swizzleMethodsIfPossible]; + + NSDictionary *message = @{@"message": @""}; + + // Verify userNotificationCenter:willPresentNotification:withCompletionHandler: + OCMExpect([self.mockMessaging appDidReceiveMessage:message]); + // Invoking delegate method should also invoke our swizzled method // The swizzled method uses the +sharedProxy, which should be - // returning our mocked proxy. + // returning our proxy. // Use non-nil, proper classes, otherwise our SDK bails out. - [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class]) - willPresentNotification:[self generateMockNotification] + [delegate userNotificationCenter:self.mockUserNotificationCenter + willPresentNotification:[self userNotificationWithMessage:message] withCompletionHandler:^(NSUInteger options) {}]; - // Verify our swizzled method was called - OCMVerify(FCM_swizzle_willPresentNotificationWithHandler); + // Verify our original method was called XCTAssertTrue(delegate.willPresentWasCalled); + + // Verify our swizzled method was called + [self.mockMessaging verify]; + #if TARGET_OS_IOS - [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class]) - didReceiveNotificationResponse:[self generateMockNotificationResponse] + // Verify userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: + + OCMExpect([self.mockMessaging appDidReceiveMessage:message]); + + [delegate userNotificationCenter:self.mockUserNotificationCenter + didReceiveNotificationResponse:[self userNotificationResponseWithMessage:message] withCompletionHandler:^{}]; - // Verify our swizzled method was called - OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler); + // Verify our original method was called XCTAssertTrue(delegate.didReceiveResponseWasCalled); -#endif -} -- (id)generateMockNotification { - // Stub out: notification.request.content.userInfo - id mockNotification = OCMClassMock([UNNotification class]); - id mockRequest = OCMClassMock([UNNotificationRequest class]); - id mockContent = OCMClassMock([UNNotificationContent class]); - OCMStub([mockContent userInfo]).andReturn(@{}); - OCMStub([mockRequest content]).andReturn(mockContent); - OCMStub([mockNotification request]).andReturn(mockRequest); - return mockNotification; + // Verify our swizzled method was called + [self.mockMessaging verify]; +#endif // TARGET_OS_IOS } -- (id)generateMockNotificationResponse { +- (id)userNotificationResponseWithMessage:(NSDictionary *)message { // Stub out: response.[mock notification above] #if TARGET_OS_IOS id mockNotificationResponse = OCMClassMock([UNNotificationResponse class]); - id mockNotification = [self generateMockNotification]; + id mockNotification = [self userNotificationWithMessage:message]; OCMStub([mockNotificationResponse notification]).andReturn(mockNotification); return mockNotificationResponse; -#else +#else // TARGET_OS_IOS return nil; -#endif +#endif // TARGET_OS_IOS +} + +- (UNNotification *)userNotificationWithMessage:(NSDictionary *)message { + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + content.userInfo = message; + id notificationRequest = OCMClassMock([UNNotificationRequest class]); + OCMStub([notificationRequest content]).andReturn(content); + id notification = OCMClassMock([UNNotification class]); + OCMStub([notification request]).andReturn(notificationRequest); + + return notification; } #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 diff --git a/Example/Podfile b/Example/Podfile index ece18d95b4d..450af7ff50e 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -3,8 +3,8 @@ #source 'https://github.com/CocoaPods/Specs.git' # Uncomment the next two lines for pre-release testing on public repo -#source 'https://github.com/Firebase/SpecsStaging.git' -#source 'https://github.com/CocoaPods/Specs.git' +source 'https://github.com/Firebase/SpecsStaging.git' +source 'https://github.com/CocoaPods/Specs.git' use_frameworks! @@ -19,7 +19,7 @@ target 'Core_Example_iOS' do # The next line is the forcing function for the Firebase pod. The Firebase # version's subspecs should depend on the component versions in their # corresponding podspec's. - pod 'Firebase/CoreOnly', '5.18.0' + pod 'Firebase/CoreOnly', '6.0.0-pre' target 'Core_Tests_iOS' do inherit! :search_paths @@ -62,17 +62,6 @@ target 'DynamicLinks_Example_iOS' do pod 'OCMock' pod 'GoogleUtilities/MethodSwizzler', :path => '../' pod 'GoogleUtilities/SwizzlerTestHelpers', :path => '../' - - # Set define to turn on the unswizzler for the unit tests - post_install do |installer_representation| - installer_representation.pods_project.targets.each do |target| - target.build_configurations.each do |config| - if config.name != 'Release' - config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'GUL_UNSWIZZLING_ENABLED=1'] - end - end - end - end end end @@ -149,7 +138,7 @@ target 'Auth_Sample' do end target 'Core_Example_macOS' do - platform :osx, '10.10' + platform :osx, '10.11' pod 'FirebaseCore', :path => '../' @@ -160,7 +149,7 @@ target 'Core_Example_macOS' do end target 'Auth_Example_macOS' do - platform :osx, '10.10' + platform :osx, '10.11' pod 'FirebaseAuth', :path => '../' @@ -171,7 +160,7 @@ target 'Auth_Example_macOS' do end target 'Database_Example_macOS' do - platform :osx, '10.10' + platform :osx, '10.11' pod 'FirebaseDatabase', :path => '../' @@ -185,7 +174,7 @@ target 'Database_Example_macOS' do end target 'Storage_Example_macOS' do - platform :osx, '10.10' + platform :osx, '10.11' pod 'FirebaseStorage', :path => '../' @@ -272,3 +261,14 @@ target 'InstanceID_Example_tvOS' do end end +# Set define to turn on GUL_UNSWIZZLING and GUL_APP_DELEGATE_TESTING for the unit tests +post_install do |installer_representation| + installer_representation.pods_project.targets.each do |target| + target.build_configurations.each do |config| + if config.name != 'Release' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'GUL_UNSWIZZLING_ENABLED=1', 'GUL_APP_DELEGATE_TESTING=1'] + end + end + end +end + diff --git a/Firebase/Auth/CHANGELOG.md b/Firebase/Auth/CHANGELOG.md index aa69d070463..7abd4d7f0e4 100644 --- a/Firebase/Auth/CHANGELOG.md +++ b/Firebase/Auth/CHANGELOG.md @@ -1,3 +1,19 @@ +# v6.0.0 +- Add support of single sign on. (#2684) +- Deprecate `reauthenticateAndRetrieveDataWithCredential:completion:`, `signInAndRetrieveDataWithCredential:completion:`, `linkAndRetrieveDataWithCredential:completion:`, `fetchProvidersForEmail:completion:`. (#2723, #2756) +- Implement oauth secret token in headful-lite. (#2663) +- Remove pendingToken from public API. (#2676) +- `GULAppDelegateSwizzler` is used for the app delegate swizzling. (#2591) + +# v5.4.2 +- Support new error code ERROR_INVALID_PROVIDER_ID. (#2629) + +# v5.4.1 +- Deprecate Microsoft and Yahoo OAuth Provider ID (#2517) +- Fix an issue where an exception was thrown when linking OAuth credentials. (#2521) +- Fix an issue where a wrong error was thrown when handling error with + FEDERATED_USER_ID_ALREADY_LINKED. (#2522) + # v5.4.0 - Add support of Generic IDP (#2405). diff --git a/Firebase/Auth/Source/FIRActionCodeSettings.m b/Firebase/Auth/Source/Auth/FIRActionCodeSettings.m similarity index 100% rename from Firebase/Auth/Source/FIRActionCodeSettings.m rename to Firebase/Auth/Source/Auth/FIRActionCodeSettings.m diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/Auth/FIRAuth.m similarity index 90% rename from Firebase/Auth/Source/FIRAuth.m rename to Firebase/Auth/Source/Auth/FIRAuth.m index efb3a487dc6..abf77061abd 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/Auth/FIRAuth.m @@ -22,16 +22,16 @@ #import #endif -#import #import #import #import #import #import #import +#import #import -#import "AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h" +#import "FIREmailPasswordAuthCredential.h" #import "FIRAdditionalUserInfo_Internal.h" #import "FIRAuthCredential_Internal.h" #import "FIRAuthDataResult_Internal.h" @@ -42,6 +42,7 @@ #import "FIRAuthKeychain.h" #import "FIRAuthOperationType.h" #import "FIRAuthSettings.h" +#import "FIRAuthStoredUserManager.h" #import "FIRUser_Internal.h" #import "FirebaseAuth.h" #import "FIRAuthBackend.h" @@ -77,8 +78,7 @@ #import "FIRAuthAPNSToken.h" #import "FIRAuthAPNSTokenManager.h" #import "FIRAuthAppCredentialManager.h" -#import "FIRAuthAppDelegateProxy.h" -#import "AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h" +#import "FIRPhoneAuthCredential_Internal.h" #import "FIRAuthNotificationManager.h" #import "FIRAuthURLPresenter.h" #endif @@ -230,7 +230,7 @@ + (FIRActionCodeOperation)actionCodeOperationForRequestType:(NSString *)requestT #pragma mark - FIRAuth #if TARGET_OS_IOS -@interface FIRAuth () +@interface FIRAuth () #else @interface FIRAuth () #endif @@ -245,6 +245,11 @@ @interface FIRAuth () */ @property(nonatomic, copy, nullable) NSString *additionalFrameworkMarker; +/** @property storedUserManager + @brief The stored user manager. + */ +@property(nonatomic, strong, nullable) FIRAuthStoredUserManager *storedUserManager; + /** @fn initWithApp: @brief Creates a @c FIRAuth instance associated with the provided @c FIRApp instance. @param app The application to associate the auth instance with. @@ -373,8 +378,7 @@ - (nullable instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)a } UIApplication *application = [applicationClass sharedApplication]; - // Initialize the shared FIRAuthAppDelegateProxy instance in the main thread if not already. - [FIRAuthAppDelegateProxy sharedInstance]; + [GULAppDelegateSwizzler proxyOriginalDelegate]; #endif // Continue with the rest of initialization in the work thread. @@ -389,12 +393,29 @@ - (nullable instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)a [FIRAuth keychainServiceNameForAppName:strongSelf->_firebaseAppName]; if (keychainServiceName) { strongSelf->_keychain = [[FIRAuthKeychain alloc] initWithService:keychainServiceName]; + strongSelf.storedUserManager = + [[FIRAuthStoredUserManager alloc] initWithServiceName:keychainServiceName]; } - FIRUser *user; + NSError *error; - if ([strongSelf getUser:&user error:&error]) { - [strongSelf updateCurrentUser:user byForce:NO savingToDisk:NO error:&error]; - self->_lastNotifiedUserToken = user.rawAccessToken; + NSString *storedUserAccessGroup = [strongSelf.storedUserManager getStoredUserAccessGroupWithError:&error]; + if (!error) { + if (!storedUserAccessGroup) { + FIRUser *user; + if ([strongSelf getUser:&user error:&error]) { + [strongSelf updateCurrentUser:user byForce:NO savingToDisk:NO error:&error]; + self->_lastNotifiedUserToken = user.rawAccessToken; + } else { + FIRLogError(kFIRLoggerAuth, @"I-AUT000001", + @"Error loading saved user when starting up: %@", error); + } + } else { + [strongSelf useUserAccessGroup:storedUserAccessGroup error:&error]; + if (error) { + FIRLogError(kFIRLoggerAuth, @"I-AUT000001", + @"Error loading saved user when starting up: %@", error); + } + } } else { FIRLogError(kFIRLoggerAuth, @"I-AUT000001", @"Error loading saved user when starting up: %@", error); @@ -412,7 +433,7 @@ - (nullable instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)a initWithApplication:application appCredentialManager:strongSelf->_appCredentialManager]; - [[FIRAuthAppDelegateProxy sharedInstance] addHandler:strongSelf]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:strongSelf]; #endif }); } @@ -577,18 +598,6 @@ - (void)signInWithEmail:(NSString *)email }]; } -- (void)signInAndRetrieveDataWithEmail:(NSString *)email - password:(NSString *)password - completion:(nullable FIRAuthDataResultCallback)completion { - dispatch_async(FIRAuthGlobalWorkQueue(), ^{ - FIRAuthDataResultCallback decoratedCallback = - [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; - [self internalSignInAndRetrieveDataWithEmail:email - password:password - completion:decoratedCallback]; - }); -} - /** @fn internalSignInAndRetrieveDataWithEmail:password:callback: @brief Signs in using an email address and password. @param email The user's email address. @@ -716,14 +725,13 @@ - (void)internalSignInAndRetrieveDataWithEmail:(nonnull NSString *)email }]; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)signInWithCredential:(FIRAuthCredential *)credential - completion:(nullable FIRAuthResultCallback)completion { - dispatch_async(FIRAuthGlobalWorkQueue(), ^{ - FIRAuthResultCallback callback = - [self signInFlowAuthResultCallbackByDecoratingCallback:completion]; - [self internalSignInWithCredential:credential callback:callback]; - }); + completion:(nullable FIRAuthDataResultCallback)completion { + [self signInAndRetrieveDataWithCredential:credential completion:completion]; } +#pragma clang diagnostic pop - (void)signInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential completion:(nullable FIRAuthDataResultCallback)completion { @@ -833,12 +841,6 @@ - (void)internalSignInAndRetrieveDataWithCredential:(FIRAuthCredential *)credent requestConfiguration:_requestConfiguration]; request.autoCreate = !isReauthentication; [credential prepareVerifyAssertionRequest:request]; - if ([credential isKindOfClass:[FIROAuthCredential class]]) { - FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential; - request.requestURI = OAuthCredential.OAuthResponseURLString; - request.sessionID = OAuthCredential.sessionID; - request.pendingToken = OAuthCredential.pendingToken; - } [FIRAuthBackend verifyAssertion:request callback:^(FIRVerifyAssertionResponse *response, NSError *error) { if (error) { @@ -851,7 +853,10 @@ - (void)internalSignInAndRetrieveDataWithCredential:(FIRAuthCredential *)credent if (response.needConfirmation) { if (callback) { NSString *email = response.email; - callback(nil, [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:email]); + FIROAuthCredential *credential = + [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:response]; + callback(nil, [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:email + updatedCredential:credential]); } return; } @@ -870,66 +875,18 @@ - (void)internalSignInAndRetrieveDataWithCredential:(FIRAuthCredential *)credent if (callback) { FIRAdditionalUserInfo *additionalUserInfo = [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response]; + FIROAuthCredential *updatedOAuthCredential = + [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:response]; FIRAuthDataResult *result = user ? [[FIRAuthDataResult alloc] initWithUser:user - additionalUserInfo:additionalUserInfo] : nil; + additionalUserInfo:additionalUserInfo + credential:updatedOAuthCredential] : nil; callback(result, error); } }]; }]; } -- (void)signInWithCredential:(FIRAuthCredential *)credential - callback:(FIRAuthResultCallback)callback { - [self signInAndRetrieveDataWithCredential:credential - completion:^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { - callback(authResult.user, error); - }]; -} - -- (void)signInAnonymouslyAndRetrieveDataWithCompletion: - (nullable FIRAuthDataResultCallback)completion { - dispatch_async(FIRAuthGlobalWorkQueue(), ^{ - FIRAuthDataResultCallback decoratedCallback = - [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; - if (self->_currentUser.anonymous) { - FIRAdditionalUserInfo *additionalUserInfo = - [[FIRAdditionalUserInfo alloc] initWithProviderID:nil - profile:nil - username:nil - isNewUser:NO]; - FIRAuthDataResult *authDataResult = - [[FIRAuthDataResult alloc] initWithUser:self->_currentUser - additionalUserInfo:additionalUserInfo]; - decoratedCallback(authDataResult, nil); - return; - } - [self internalSignInAnonymouslyWithCompletion:^(FIRSignUpNewUserResponse *_Nullable response, - NSError *_Nullable error) { - if (error) { - decoratedCallback(nil, error); - return; - } - [self completeSignInWithAccessToken:response.IDToken - accessTokenExpirationDate:response.approximateExpirationDate - refreshToken:response.refreshToken - anonymous:YES - callback:^(FIRUser *_Nullable user, NSError *_Nullable error) { - FIRAdditionalUserInfo *additionalUserInfo = - [[FIRAdditionalUserInfo alloc] initWithProviderID:nil - profile:nil - username:nil - isNewUser:YES]; - FIRAuthDataResult *authDataResult = - [[FIRAuthDataResult alloc] initWithUser:user - additionalUserInfo:additionalUserInfo]; - decoratedCallback(authDataResult, nil); - }]; - }]; - }); -} - - (void)signInAnonymouslyWithCompletion:(nullable FIRAuthDataResultCallback)completion { dispatch_async(FIRAuthGlobalWorkQueue(), ^{ FIRAuthDataResultCallback decoratedCallback = @@ -978,15 +935,6 @@ - (void)signInWithCustomToken:(NSString *)token }); } -- (void)signInAndRetrieveDataWithCustomToken:(NSString *)token - completion:(nullable FIRAuthDataResultCallback)completion { - dispatch_async(FIRAuthGlobalWorkQueue(), ^{ - FIRAuthDataResultCallback decoratedCallback = - [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; - [self internalSignInAndRetrieveDataWithCustomToken:token completion:decoratedCallback]; - }); -} - - (void)createUserWithEmail:(NSString *)email password:(NSString *)password completion:(nullable FIRAuthDataResultCallback)completion { @@ -1020,40 +968,6 @@ - (void)createUserWithEmail:(NSString *)email }); } -- (void)createUserAndRetrieveDataWithEmail:(NSString *)email - password:(NSString *)password - completion:(nullable FIRAuthDataResultCallback)completion { - dispatch_async(FIRAuthGlobalWorkQueue(), ^{ - FIRAuthDataResultCallback decoratedCallback = - [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; - [self internalCreateUserWithEmail:email - password:password - completion:^(FIRSignUpNewUserResponse *_Nullable response, - NSError *_Nullable error) { - if (error) { - decoratedCallback(nil, error); - return; - } - - [self completeSignInWithAccessToken:response.IDToken - accessTokenExpirationDate:response.approximateExpirationDate - refreshToken:response.refreshToken - anonymous:NO - callback:^(FIRUser *_Nullable user, NSError *_Nullable error) { - FIRAdditionalUserInfo *additionalUserInfo = - [[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID - profile:nil - username:nil - isNewUser:YES]; - FIRAuthDataResult *authDataResult = - [[FIRAuthDataResult alloc] initWithUser:user - additionalUserInfo:additionalUserInfo]; - decoratedCallback(authDataResult, nil); - }]; - }]; - }); -} - - (void)confirmPasswordResetWithCode:(NSString *)code newPassword:(NSString *)newPassword completion:(FIRConfirmPasswordResetCallback)completion { @@ -1420,19 +1334,47 @@ - (nullable NSData *)APNSToken { return result; } -- (void)setAPNSToken:(nullable NSData *)APNSToken { - [self setAPNSToken:APNSToken type:FIRAuthAPNSTokenTypeUnknown]; +#pragma mark - UIApplicationDelegate + +- (void)application:(UIApplication *)application +didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + [self setAPNSToken:deviceToken type:FIRAuthAPNSTokenTypeUnknown]; } -- (void)setAPNSToken:(NSData *)token type:(FIRAuthAPNSTokenType)type { +- (void)application:(UIApplication *)application +didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ - self->_tokenManager.token = [[FIRAuthAPNSToken alloc] initWithData:token type:type]; + [self->_tokenManager cancelWithError:error]; }); } -- (void)handleAPNSTokenError:(NSError *)error { +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + [self canHandleNotification:userInfo]; +} + +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo { + [self canHandleNotification:userInfo]; +} + +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + return [self canHandleURL:url]; +} + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(nullable NSString *)sourceApplication + annotation:(id)annotation { + return [self canHandleURL:url]; +} + +- (void)setAPNSToken:(NSData *)token type:(FIRAuthAPNSTokenType)type { dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ - [self->_tokenManager cancelWithError:error]; + self->_tokenManager.token = [[FIRAuthAPNSToken alloc] initWithData:token type:type]; }); } @@ -1855,26 +1797,40 @@ - (BOOL)updateCurrentUser:(nullable FIRUser *)user /** @fn saveUser:error: @brief Persists user. @param user The user to save. - @param error Return value for any error which occurs. + @param outError Return value for any error which occurs. @return @YES on success, @NO otherwise. */ - (BOOL)saveUser:(FIRUser *)user - error:(NSError *_Nullable *_Nullable)error { + error:(NSError *_Nullable *_Nullable)outError { BOOL success; - NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; - if (!user) { - success = [_keychain removeDataForKey:userKey error:error]; + if (!self.userAccessGroup) { + NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; + if (!user) { + success = [_keychain removeDataForKey:userKey error:outError]; + } else { + // Encode the user object. + NSMutableData *archiveData = [NSMutableData data]; + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiveData]; + [archiver encodeObject:user forKey:userKey]; + [archiver finishEncoding]; + + // Save the user object's encoded value. + success = [_keychain setData:archiveData forKey:userKey error:outError]; + } } else { - // Encode the user object. - NSMutableData *archiveData = [NSMutableData data]; - NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiveData]; - [archiver encodeObject:user forKey:userKey]; - [archiver finishEncoding]; - - // Save the user object's encoded value. - success = [_keychain setData:archiveData forKey:userKey error:error]; + if (!user) { + success = [self.storedUserManager removeStoredUserForAccessGroup:self.userAccessGroup + projectIdentifier:self.app.options.APIKey + error:outError]; + } else { + success = [self.storedUserManager setStoredUser:user + forAccessGroup:self.userAccessGroup + projectIdentifier:self.app.options.APIKey + error:outError]; + } } + return success; } @@ -1887,26 +1843,44 @@ - (BOOL)saveUser:(FIRUser *)user */ - (BOOL)getUser:(FIRUser *_Nullable *)outUser error:(NSError *_Nullable *_Nullable)error { - NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; + if (!self.userAccessGroup) { + NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; - NSError *keychainError; - NSData *encodedUserData = [_keychain dataForKey:userKey error:&keychainError]; - if (keychainError) { - if (error) { - *error = keychainError; + NSError *keychainError; + NSData *encodedUserData = [_keychain dataForKey:userKey error:&keychainError]; + if (keychainError) { + if (error) { + *error = keychainError; + } + return NO; } - return NO; - } - if (!encodedUserData) { - *outUser = nil; + if (!encodedUserData) { + *outUser = nil; + return YES; + } + NSKeyedUnarchiver *unarchiver = + [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedUserData]; + FIRUser *user = [unarchiver decodeObjectOfClass:[FIRUser class] forKey:userKey]; + user.auth = self; + *outUser = user; + return YES; + } else { + FIRUser *user = [self.storedUserManager getStoredUserForAccessGroup:self.userAccessGroup + projectIdentifier:self.app.options.APIKey + error:error]; + user.auth = self; + *outUser = user; + if (user) { + return YES; + } else { + if (error && *error) { + return NO; + } else { + return YES; + } + } } - NSKeyedUnarchiver *unarchiver = - [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedUserData]; - FIRUser *user = [unarchiver decodeObjectOfClass:[FIRUser class] forKey:userKey]; - user.auth = self; - *outUser = user; - return YES; } #pragma mark - Interoperability @@ -2011,6 +1985,58 @@ - (nullable NSString *)getUserID { return _currentUser.uid; } +#pragma mark - Keychain sharing + +- (BOOL)useUserAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError { + BOOL success; + success = [self.storedUserManager setStoredUserAccessGroup:accessGroup error:outError]; + if (!success) { + return NO; + } + + FIRUser *user = [self getStoredUserForAccessGroup:accessGroup error:outError]; + if (!user && outError && *outError) { + return NO; + } + success = [self updateCurrentUser:user byForce:NO savingToDisk:NO error:outError]; + if (!success) { + return NO; + } + + if(_userAccessGroup == nil && accessGroup != nil) { + NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; + [_keychain removeDataForKey:userKey error:outError]; + } + _userAccessGroup = accessGroup; + self->_lastNotifiedUserToken = user.rawAccessToken; + + return YES; +} + +- (FIRUser *)getStoredUserForAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError { + FIRUser *user; + if (!accessGroup) { + NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName]; + NSData *encodedUserData = [_keychain dataForKey:userKey error:outError]; + if (!encodedUserData) { + return nil; + } + + NSKeyedUnarchiver *unarchiver = + [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedUserData]; + user = [unarchiver decodeObjectOfClass:[FIRUser class] forKey:userKey]; + user.auth = self; + } else { + user = [self.storedUserManager getStoredUserForAccessGroup:self.userAccessGroup + projectIdentifier:self.app.options.APIKey + error:outError]; + } + + return user; +} + @end NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRAuthDataResult.m b/Firebase/Auth/Source/Auth/FIRAuthDataResult.m similarity index 72% rename from Firebase/Auth/Source/FIRAuthDataResult.m rename to Firebase/Auth/Source/Auth/FIRAuthDataResult.m index 9d8e5988d0e..ec72d76365f 100644 --- a/Firebase/Auth/Source/FIRAuthDataResult.m +++ b/Firebase/Auth/Source/Auth/FIRAuthDataResult.m @@ -18,6 +18,7 @@ #import "FIRAdditionalUserInfo.h" #import "FIRUser.h" +#import "FIROAuthCredential.h" NS_ASSUME_NONNULL_BEGIN @@ -33,12 +34,24 @@ @implementation FIRAuthDataResult */ static NSString *const kUserCodingKey = @"user"; +/** @var kCredentialCodingKey + @brief The key used to encode the credential for NSSecureCoding. + */ +static NSString *const kCredentialCodingKey = @"credential"; + - (nullable instancetype)initWithUser:(nullable FIRUser *)user additionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo { + return [self initWithUser:user additionalUserInfo:additionalUserInfo credential:nil]; +} + +- (nullable instancetype)initWithUser:(nullable FIRUser *)user + additionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo + credential:(nullable FIROAuthCredential *)credential { self = [super init]; if (self) { _additionalUserInfo = additionalUserInfo; _user = user; + _credential = credential; } return self; } @@ -55,13 +68,16 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { FIRAdditionalUserInfo *additionalUserInfo = [aDecoder decodeObjectOfClass:[FIRAdditionalUserInfo class] forKey:kAdditionalUserInfoCodingKey]; - - return [self initWithUser:user additionalUserInfo:additionalUserInfo]; + FIROAuthCredential *credential = + [aDecoder decodeObjectOfClass:[FIROAuthCredential class] + forKey:kCredentialCodingKey]; + return [self initWithUser:user additionalUserInfo:additionalUserInfo credential:credential]; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_user forKey:kUserCodingKey]; [aCoder encodeObject:_additionalUserInfo forKey:kAdditionalUserInfoCodingKey]; + [aCoder encodeObject:_credential forKey:kCredentialCodingKey]; } @end diff --git a/Firebase/Auth/Source/FIRAuthDataResult_Internal.h b/Firebase/Auth/Source/Auth/FIRAuthDataResult_Internal.h similarity index 69% rename from Firebase/Auth/Source/FIRAuthDataResult_Internal.h rename to Firebase/Auth/Source/Auth/FIRAuthDataResult_Internal.h index b02bf59de83..3212e15bc9e 100644 --- a/Firebase/Auth/Source/FIRAuthDataResult_Internal.h +++ b/Firebase/Auth/Source/Auth/FIRAuthDataResult_Internal.h @@ -25,8 +25,18 @@ NS_ASSUME_NONNULL_BEGIN @param user The signed in user reference. @param additionalUserInfo The additional user info if available. */ +- (nullable instancetype)initWithUser:(nullable FIRUser *)user + additionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo; + +/** @fn initWithUser:additionalUserInfo: + @brief Designated initializer. + @param user The signed in user reference. + @param additionalUserInfo The additional user info if available. + @param credential The updated OAuth credential if available. + */ - (nullable instancetype)initWithUser:(nullable FIRUser *)user additionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo + credential:(nullable FIROAuthCredential *)credential NS_DESIGNATED_INITIALIZER; @end diff --git a/Firebase/Auth/Source/FIRAuthDispatcher.h b/Firebase/Auth/Source/Auth/FIRAuthDispatcher.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthDispatcher.h rename to Firebase/Auth/Source/Auth/FIRAuthDispatcher.h diff --git a/Firebase/Auth/Source/FIRAuthDispatcher.m b/Firebase/Auth/Source/Auth/FIRAuthDispatcher.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthDispatcher.m rename to Firebase/Auth/Source/Auth/FIRAuthDispatcher.m diff --git a/Firebase/Auth/Source/FIRAuthGlobalWorkQueue.h b/Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthGlobalWorkQueue.h rename to Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.h diff --git a/Firebase/Auth/Source/FIRAuthGlobalWorkQueue.m b/Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthGlobalWorkQueue.m rename to Firebase/Auth/Source/Auth/FIRAuthGlobalWorkQueue.m diff --git a/Firebase/Auth/Source/FIRAuthOperationType.h b/Firebase/Auth/Source/Auth/FIRAuthOperationType.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthOperationType.h rename to Firebase/Auth/Source/Auth/FIRAuthOperationType.h diff --git a/Firebase/Auth/Source/FIRAuthSerialTaskQueue.h b/Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthSerialTaskQueue.h rename to Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.h diff --git a/Firebase/Auth/Source/FIRAuthSerialTaskQueue.m b/Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthSerialTaskQueue.m rename to Firebase/Auth/Source/Auth/FIRAuthSerialTaskQueue.m diff --git a/Firebase/Auth/Source/FIRAuthSettings.m b/Firebase/Auth/Source/Auth/FIRAuthSettings.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthSettings.m rename to Firebase/Auth/Source/Auth/FIRAuthSettings.m diff --git a/Firebase/Auth/Source/FIRAuthTokenResult.m b/Firebase/Auth/Source/Auth/FIRAuthTokenResult.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthTokenResult.m rename to Firebase/Auth/Source/Auth/FIRAuthTokenResult.m diff --git a/Firebase/Auth/Source/FIRAuthTokenResult_Internal.h b/Firebase/Auth/Source/Auth/FIRAuthTokenResult_Internal.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthTokenResult_Internal.h rename to Firebase/Auth/Source/Auth/FIRAuthTokenResult_Internal.h diff --git a/Firebase/Auth/Source/FIRAuth_Internal.h b/Firebase/Auth/Source/Auth/FIRAuth_Internal.h similarity index 100% rename from Firebase/Auth/Source/FIRAuth_Internal.h rename to Firebase/Auth/Source/Auth/FIRAuth_Internal.h diff --git a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailAuthProvider.m b/Firebase/Auth/Source/AuthProvider/Email/FIREmailAuthProvider.m similarity index 95% rename from Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailAuthProvider.m rename to Firebase/Auth/Source/AuthProvider/Email/FIREmailAuthProvider.m index ff7e0042ba0..373c0b141c1 100644 --- a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailAuthProvider.m +++ b/Firebase/Auth/Source/AuthProvider/Email/FIREmailAuthProvider.m @@ -18,8 +18,6 @@ #import "FIREmailPasswordAuthCredential.h" -// FIREmailPasswordAuthProviderID is defined in FIRAuthProvider.m. - NS_ASSUME_NONNULL_BEGIN @implementation FIREmailAuthProvider diff --git a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h b/Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.h similarity index 100% rename from Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h rename to Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.h diff --git a/Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.m b/Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.m rename to Firebase/Auth/Source/AuthProvider/Email/FIREmailPasswordAuthCredential.m diff --git a/Firebase/Auth/Source/FIRAuthCredential.m b/Firebase/Auth/Source/AuthProvider/FIRAuthCredential.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthCredential.m rename to Firebase/Auth/Source/AuthProvider/FIRAuthCredential.m diff --git a/Firebase/Auth/Source/FIRAuthCredential_Internal.h b/Firebase/Auth/Source/AuthProvider/FIRAuthCredential_Internal.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthCredential_Internal.h rename to Firebase/Auth/Source/AuthProvider/FIRAuthCredential_Internal.h diff --git a/Firebase/Auth/Source/FIRAuthProvider.m b/Firebase/Auth/Source/AuthProvider/FIRAuthProvider.m similarity index 95% rename from Firebase/Auth/Source/FIRAuthProvider.m rename to Firebase/Auth/Source/AuthProvider/FIRAuthProvider.m index e382d4e751a..33618505ae2 100644 --- a/Firebase/Auth/Source/FIRAuthProvider.m +++ b/Firebase/Auth/Source/AuthProvider/FIRAuthProvider.m @@ -27,9 +27,6 @@ // Declared 'extern' in FIREmailAuthProvider.h NSString *const FIREmailAuthProviderID = @"password"; -// Declared 'extern' in FIREmailAuthProvider.h -NSString *const FIREmailPasswordAuthProviderID = @"password"; - // Declared 'extern' in FIRTwitterAuthProvider.h NSString *const FIRTwitterAuthProviderID = @"twitter.com"; diff --git a/Firebase/Auth/Source/AuthProviders/Facebook/FIRFacebookAuthCredential.h b/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.h similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Facebook/FIRFacebookAuthCredential.h rename to Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.h diff --git a/Firebase/Auth/Source/AuthProviders/Facebook/FIRFacebookAuthCredential.m b/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Facebook/FIRFacebookAuthCredential.m rename to Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthCredential.m diff --git a/Firebase/Auth/Source/AuthProviders/Facebook/FIRFacebookAuthProvider.m b/Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthProvider.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Facebook/FIRFacebookAuthProvider.m rename to Firebase/Auth/Source/AuthProvider/Facebook/FIRFacebookAuthProvider.m diff --git a/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.h b/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.h similarity index 100% rename from Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.h rename to Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.h diff --git a/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.m b/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.m rename to Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthCredential.m diff --git a/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthProvider.m b/Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthProvider.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthProvider.m rename to Firebase/Auth/Source/AuthProvider/GameCenter/FIRGameCenterAuthProvider.m diff --git a/Firebase/Auth/Source/AuthProviders/GitHub/FIRGitHubAuthCredential.h b/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.h similarity index 100% rename from Firebase/Auth/Source/AuthProviders/GitHub/FIRGitHubAuthCredential.h rename to Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.h diff --git a/Firebase/Auth/Source/AuthProviders/GitHub/FIRGitHubAuthCredential.m b/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/GitHub/FIRGitHubAuthCredential.m rename to Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthCredential.m diff --git a/Firebase/Auth/Source/AuthProviders/GitHub/FIRGitHubAuthProvider.m b/Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthProvider.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/GitHub/FIRGitHubAuthProvider.m rename to Firebase/Auth/Source/AuthProvider/GitHub/FIRGitHubAuthProvider.m diff --git a/Firebase/Auth/Source/AuthProviders/Google/FIRGoogleAuthCredential.h b/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.h similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Google/FIRGoogleAuthCredential.h rename to Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.h diff --git a/Firebase/Auth/Source/AuthProviders/Google/FIRGoogleAuthCredential.m b/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Google/FIRGoogleAuthCredential.m rename to Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthCredential.m diff --git a/Firebase/Auth/Source/AuthProviders/Google/FIRGoogleAuthProvider.m b/Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthProvider.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Google/FIRGoogleAuthProvider.m rename to Firebase/Auth/Source/AuthProvider/Google/FIRGoogleAuthProvider.m diff --git a/Firebase/Auth/Source/AuthProviders/OAuth/FIROAuthCredential.m b/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential.m similarity index 72% rename from Firebase/Auth/Source/AuthProviders/OAuth/FIROAuthCredential.m rename to Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential.m index ebea7be8fa3..069d4f36608 100644 --- a/Firebase/Auth/Source/AuthProviders/OAuth/FIROAuthCredential.m +++ b/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential.m @@ -20,6 +20,7 @@ #import "FIRAuthExceptionUtils.h" #import "FIROAuthCredential_Internal.h" #import "FIRVerifyAssertionRequest.h" +#import "FIRVerifyAssertionResponse.h" NS_ASSUME_NONNULL_BEGIN @@ -40,12 +41,14 @@ - (nullable instancetype)initWithProvider:(NSString *)provider { - (instancetype)initWithProviderID:(NSString *)providerID IDToken:(nullable NSString *)IDToken accessToken:(nullable NSString *)accessToken + secret:(nullable NSString *)secret pendingToken:(nullable NSString *)pendingToken { self = [super initWithProvider:providerID]; if (self) { _IDToken = IDToken; _accessToken = accessToken; _pendingToken = pendingToken; + _secret = secret; } return self; } @@ -53,7 +56,8 @@ - (instancetype)initWithProviderID:(NSString *)providerID - (instancetype)initWithProviderID:(NSString *)providerID sessionID:(NSString *)sessionID OAuthResponseURLString:(NSString *)OAuthResponseURLString { - self = [self initWithProviderID:providerID IDToken:nil accessToken:nil pendingToken:nil]; + self = + [self initWithProviderID:providerID IDToken:nil accessToken:nil secret:nil pendingToken:nil]; if (self) { _OAuthResponseURLString = OAuthResponseURLString; _sessionID = sessionID; @@ -61,9 +65,26 @@ - (instancetype)initWithProviderID:(NSString *)providerID return self; } + +- (nullable instancetype)initWithVerifyAssertionResponse:(FIRVerifyAssertionResponse *)response { + if (response.oauthIDToken.length || response.oauthAccessToken.length || + response.oauthSecretToken.length) { + return [self initWithProviderID:response.providerID + IDToken:response.oauthIDToken + accessToken:response.oauthAccessToken + secret:response.oauthSecretToken + pendingToken:response.pendingToken]; + } + return nil; +} + - (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { request.providerIDToken = _IDToken; request.providerAccessToken = _accessToken; + request.requestURI = _OAuthResponseURLString; + request.sessionID = _sessionID; + request.providerOAuthTokenSecret = _secret; + request.pendingToken = _pendingToken; } #pragma mark - NSSecureCoding @@ -76,9 +97,11 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { NSString *IDToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"IDToken"]; NSString *accessToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"accessToken"]; NSString *pendingToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"pendingToken"]; + NSString *secret = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"secret"]; self = [self initWithProviderID:self.provider IDToken:IDToken accessToken:accessToken + secret:secret pendingToken:pendingToken]; return self; } @@ -87,6 +110,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.IDToken forKey:@"IDToken"]; [aCoder encodeObject:self.accessToken forKey:@"accessToken"]; [aCoder encodeObject:self.pendingToken forKey:@"pendingToken"]; + [aCoder encodeObject:self.secret forKey:@"secret"]; } @end diff --git a/Firebase/Auth/Source/AuthProviders/OAuth/FIROAuthCredential_Internal.h b/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential_Internal.h similarity index 77% rename from Firebase/Auth/Source/AuthProviders/OAuth/FIROAuthCredential_Internal.h rename to Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential_Internal.h index bcc3248ebba..6bc7f4e854b 100644 --- a/Firebase/Auth/Source/AuthProviders/OAuth/FIROAuthCredential_Internal.h +++ b/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthCredential_Internal.h @@ -18,6 +18,8 @@ #import "FIROAuthCredential.h" +@class FIRVerifyAssertionResponse; + NS_ASSUME_NONNULL_BEGIN /** @extension FIROAuthCredential @@ -35,16 +37,23 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, readonly, nullable) NSString *sessionID; -/** @fn initWithProviderId:IDToken:accessToken:pendingToken +/** @property pendingToken + @brief The pending token used when completing the headful-lite flow. + */ +@property(nonatomic, readonly, nullable) NSString *pendingToken; + +/** @fn initWithProviderId:IDToken:accessToken:secret:pendingToken @brief Designated initializer. @param providerID The provider ID associated with the credential being created. @param IDToken The ID Token associated with the credential being created. @param accessToken The access token associated with the credential being created. + @param secret The secret associated with the credential being created. @param pendingToken The pending token associated with the credential being created. */ - (instancetype)initWithProviderID:(NSString *)providerID IDToken:(nullable NSString *)IDToken accessToken:(nullable NSString *)accessToken + secret:(nullable NSString *)secret pendingToken:(nullable NSString *)pendingToken NS_DESIGNATED_INITIALIZER; /** @fn initWithProviderId:sessionID:OAuthResponseURLString: @@ -57,6 +66,12 @@ NS_ASSUME_NONNULL_BEGIN sessionID:(NSString *)sessionID OAuthResponseURLString:(NSString *)OAuthResponseURLString; +/** @fn initWithVerifyAssertionResponse + @brief Intitializer which takes an verifyAssertion response. + @param response The verifyAssertion Response to create the credential instance. + */ +- (nullable instancetype)initWithVerifyAssertionResponse:(FIRVerifyAssertionResponse *)response; + @end NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/AuthProviders/OAuth/FIROAuthProvider.m b/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthProvider.m similarity index 94% rename from Firebase/Auth/Source/AuthProviders/OAuth/FIROAuthProvider.m rename to Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthProvider.m index 8cd1e2811c5..6f2fa5092f2 100644 --- a/Firebase/Auth/Source/AuthProviders/OAuth/FIROAuthProvider.m +++ b/Firebase/Auth/Source/AuthProvider/OAuth/FIROAuthProvider.m @@ -17,7 +17,9 @@ #include #import "FIROAuthProvider.h" -#import "FIRApp.h" +#import +#import + #import "FIRAuthBackend.h" #import "FIRAuth_Internal.h" #import "FIRAuthErrorUtils.h" @@ -26,7 +28,6 @@ #import "FIRAuthWebUtils.h" #import "FIROAuthCredential_Internal.h" #import "FIROAuthCredential.h" -#import "FIROptions.h" #if TARGET_OS_IOS #import "FIRAuthURLPresenter.h" @@ -37,7 +38,7 @@ /** @typedef FIRHeadfulLiteURLCallBack @brief The callback invoked at the end of the flow to fetch a headful-lite URL. @param headfulLiteURL The headful lite URL. - @param error The error that occured while fetching the headful-lite, if any. + @param error The error that occurred while fetching the headful-lite, if any. */ typedef void (^FIRHeadfulLiteURLCallBack)(NSURL *_Nullable headfulLiteURL, NSError *_Nullable error); @@ -64,22 +65,13 @@ @implementation FIROAuthProvider { NSString *_callbackScheme; } -+ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID - IDToken:(NSString *)IDToken - accessToken:(nullable NSString *)accessToken - pendingToken:(nullable NSString *)pendingToken { - return [[FIROAuthCredential alloc] initWithProviderID:providerID - IDToken:IDToken - accessToken:accessToken - pendingToken:pendingToken]; -} - + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID IDToken:(NSString *)IDToken accessToken:(nullable NSString *)accessToken { return [[FIROAuthCredential alloc] initWithProviderID:providerID IDToken:IDToken accessToken:accessToken + secret:nil pendingToken:nil]; } @@ -88,6 +80,7 @@ + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID return [[FIROAuthCredential alloc] initWithProviderID:providerID IDToken:nil accessToken:accessToken + secret:nil pendingToken:nil]; } @@ -167,7 +160,7 @@ - (void)getCredentialWithUIDelegate:(nullable id)UIDelegate #pragma mark - Internal Methods /** @fn initWithProviderID:auth: - @brief returns an instance of @c FIROAuthProvider assocaited with the provided auth instance. + @brief returns an instance of @c FIROAuthProvider associated with the provided auth instance. @param auth The Auth instance to be associated with the OAuthProvider instance. @return An Instance of @c FIROAuthProvider. */ @@ -314,7 +307,7 @@ - (nullable NSString *)customParametersStringWithError:(NSError *_Nullable *_Nul } /** @fn hashforString: - @brief Returns the SHA256 hash representaion of a given string object. + @brief Returns the SHA256 hash representation of a given string object. @param string The string for which a SHA256 hash is desired. @return An hexadecimal string representation of the SHA256 hash. */ diff --git a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthCredential.m b/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthCredential.m rename to Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential.m diff --git a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h b/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h rename to Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h diff --git a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m b/Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthProvider.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m rename to Firebase/Auth/Source/AuthProvider/Phone/FIRPhoneAuthProvider.m diff --git a/Firebase/Auth/Source/AuthProviders/Twitter/FIRTwitterAuthCredential.h b/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.h similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Twitter/FIRTwitterAuthCredential.h rename to Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.h diff --git a/Firebase/Auth/Source/AuthProviders/Twitter/FIRTwitterAuthCredential.m b/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Twitter/FIRTwitterAuthCredential.m rename to Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthCredential.m diff --git a/Firebase/Auth/Source/AuthProviders/Twitter/FIRTwitterAuthProvider.m b/Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthProvider.m similarity index 100% rename from Firebase/Auth/Source/AuthProviders/Twitter/FIRTwitterAuthProvider.m rename to Firebase/Auth/Source/AuthProvider/Twitter/FIRTwitterAuthProvider.m diff --git a/Firebase/Auth/Source/RPCs/FIRAuthBackend.h b/Firebase/Auth/Source/Backend/FIRAuthBackend.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRAuthBackend.h rename to Firebase/Auth/Source/Backend/FIRAuthBackend.h diff --git a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m b/Firebase/Auth/Source/Backend/FIRAuthBackend.m similarity index 97% rename from Firebase/Auth/Source/RPCs/FIRAuthBackend.m rename to Firebase/Auth/Source/Backend/FIRAuthBackend.m index 2ec672696aa..665570bd5f4 100644 --- a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m +++ b/Firebase/Auth/Source/Backend/FIRAuthBackend.m @@ -59,9 +59,9 @@ #import "FIRVerifyPhoneNumberRequest.h" #import "FIRVerifyPhoneNumberResponse.h" -#import "../AuthProviders/OAuth/FIROAuthCredential_Internal.h" +#import "FIROAuthCredential_Internal.h" #if TARGET_OS_IOS -#import "../AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h" +#import "FIRPhoneAuthCredential_Internal.h" #import "FIRPhoneAuthProvider.h" #endif @@ -128,6 +128,12 @@ */ static NSString *const kErrorMessageKey = @"message"; +/** @var kReturnIDPCredentialErrorMessageKey + @brief The key for "errorMessage" value in JSON responses from the server, In case + returnIDPCredential of a verifyAssertion request is set to @YES. + */ +static NSString *const kReturnIDPCredentialErrorMessageKey = @"errorMessage"; + /** @var kUserNotFoundErrorMessage @brief This is the error message returned when the user is not found, which means the user account has been deleted given the token was once valid. @@ -288,9 +294,15 @@ */ static NSString *const kUnauthorizedDomainErrorMessage = @"UNAUTHORIZED_DOMAIN"; +/** @var kInvalidProviderIDErrorMessage + @brief This is the error message the server will respond with if the provider id given for the + web operation is invalid. + */ +static NSString *const kInvalidProviderIDErrorMessage = @"INVALID_PROVIDER_ID"; + /** @var kInvalidDynamicLinkDomainErrorMessage - @brief This is the error message the server will respond with if the dynamic link domain provided - in the request is invalid. + @brief This is the error message the server will respond with if the dynamic link domain + provided in the request is invalid. */ static NSString *const kInvalidDynamicLinkDomainErrorMessage = @"INVALID_DYNAMIC_LINK_DOMAIN"; @@ -925,18 +937,15 @@ - (void)postWithRequest:(id)request if ([request isKindOfClass:[FIRVerifyAssertionRequest class]]) { FIRVerifyAssertionRequest *verifyAssertionRequest = (FIRVerifyAssertionRequest *)request; if (verifyAssertionRequest.returnIDPCredential) { - NSDictionary *errorDictionary = dictionary[kErrorKey]; - if ([errorDictionary isKindOfClass:[NSDictionary class]]) { - id errorMessage = errorDictionary[kErrorMessageKey]; - if ([errorMessage isKindOfClass:[NSString class]]) { - NSString *errorString = (NSString *)errorMessage; - NSError *clientError = [[self class] clientErrorWithServerErrorMessage:errorString - errorDictionary:errorDictionary - response:response]; - if (clientError) { - callback(clientError); - return; - } + NSString *errorMessage = dictionary[kReturnIDPCredentialErrorMessageKey]; + if ([errorMessage isKindOfClass:[NSString class]]) { + NSString *errorString = (NSString *)errorMessage; + NSError *clientError = [[self class] clientErrorWithServerErrorMessage:errorString + errorDictionary:@{} + response:response]; + if (clientError) { + callback(clientError); + return; } } } @@ -1059,10 +1068,7 @@ + (nullable NSError *)clientErrorWithServerErrorMessage:(NSString *)serverErrorM if ([response isKindOfClass:[FIRVerifyAssertionResponse class]]) { FIRVerifyAssertionResponse *verifyAssertion = (FIRVerifyAssertionResponse *)response; credential = - [[FIROAuthCredential alloc] initWithProviderID:verifyAssertion.providerID - IDToken:verifyAssertion.oauthIDToken - accessToken:verifyAssertion.oauthAccessToken - pendingToken:verifyAssertion.pendingToken]; + [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:verifyAssertion]; email = verifyAssertion.email; } return [FIRAuthErrorUtils credentialAlreadyInUseErrorWithMessage:serverDetailErrorMessage @@ -1114,6 +1120,10 @@ + (nullable NSError *)clientErrorWithServerErrorMessage:(NSString *)serverErrorM return [FIRAuthErrorUtils invalidContinueURIErrorWithMessage:serverDetailErrorMessage]; } + if ([shortErrorMessage isEqualToString:kInvalidProviderIDErrorMessage]) { + return [FIRAuthErrorUtils invalidProviderIDErrorWithMessage:serverDetailErrorMessage]; + } + if ([shortErrorMessage isEqualToString:kInvalidDynamicLinkDomainErrorMessage]) { return [FIRAuthErrorUtils invalidDynamicLinkDomainErrorWithMessage:serverDetailErrorMessage]; } diff --git a/Firebase/Auth/Source/RPCs/FIRAuthRPCRequest.h b/Firebase/Auth/Source/Backend/FIRAuthRPCRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRAuthRPCRequest.h rename to Firebase/Auth/Source/Backend/FIRAuthRPCRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRAuthRPCResponse.h b/Firebase/Auth/Source/Backend/FIRAuthRPCResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRAuthRPCResponse.h rename to Firebase/Auth/Source/Backend/FIRAuthRPCResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRAuthRequestConfiguration.h b/Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRAuthRequestConfiguration.h rename to Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.h diff --git a/Firebase/Auth/Source/RPCs/FIRAuthRequestConfiguration.m b/Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRAuthRequestConfiguration.m rename to Firebase/Auth/Source/Backend/FIRAuthRequestConfiguration.m diff --git a/Firebase/Auth/Source/RPCs/FIRIdentityToolkitRequest.h b/Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRIdentityToolkitRequest.h rename to Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRIdentityToolkitRequest.m b/Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRIdentityToolkitRequest.m rename to Firebase/Auth/Source/Backend/FIRIdentityToolkitRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRCreateAuthURIRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRCreateAuthURIRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRCreateAuthURIRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRCreateAuthURIRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRCreateAuthURIResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRCreateAuthURIResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRDeleteAccountRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRDeleteAccountRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRDeleteAccountRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRDeleteAccountRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRDeleteAccountResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRDeleteAccountResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRDeleteAccountResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRDeleteAccountResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRDeleteAccountResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.h b/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.m b/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIREmailLinkSignInRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.h b/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.m b/Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIREmailLinkSignInResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIREmailLinkSignInResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRGetAccountInfoRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetAccountInfoRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRGetAccountInfoRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetAccountInfoRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRGetAccountInfoResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetAccountInfoResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRGetAccountInfoResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetAccountInfoResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRGetAccountInfoResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h similarity index 98% rename from Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h index 3130c7898fb..2c5faf0bda4 100644 --- a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.h +++ b/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h @@ -99,7 +99,7 @@ typedef NS_ENUM(NSInteger, FIRGetOOBConfirmationCodeRequestType) { /** @property dynamicLinkDomain @brief The Firebase Dynamic Link domain used for out of band code flow. */ -@property (copy, nonatomic, nullable) NSString *dynamicLinkDomain; +@property(copy, nonatomic, nullable) NSString *dynamicLinkDomain; /** @fn passwordResetRequestWithEmail:actionCodeSettings:requestConfiguration: diff --git a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetOOBConfirmationCodeResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRGetOOBConfirmationCodeResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRGetProjectConfigRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetProjectConfigRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRGetProjectConfigRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetProjectConfigRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRGetProjectConfigResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetProjectConfigResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRGetProjectConfigResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRGetProjectConfigResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRGetProjectConfigResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRResetPasswordRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRResetPasswordRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRResetPasswordRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRResetPasswordRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRResetPasswordRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRResetPasswordResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRResetPasswordResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRResetPasswordResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRResetPasswordResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRResetPasswordResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRSecureTokenRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSecureTokenRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRSecureTokenRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSecureTokenRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRSecureTokenRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRSecureTokenResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSecureTokenResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRSecureTokenResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSecureTokenResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRSecureTokenResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSendVerificationCodeRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSendVerificationCodeResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRSendVerificationCodeResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSendVerificationCodeResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRSendVerificationCodeResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRSetAccountInfoRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSetAccountInfoRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRSetAccountInfoRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSetAccountInfoRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRSetAccountInfoResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSetAccountInfoResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRSetAccountInfoResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSetAccountInfoResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRSetAccountInfoResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRSignInWithGameCenterResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRSignUpNewUserRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSignUpNewUserRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRSignUpNewUserRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSignUpNewUserRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRSignUpNewUserResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSignUpNewUserResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRSignUpNewUserResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRSignUpNewUserResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRSignUpNewUserResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyAssertionRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.m similarity index 96% rename from Firebase/Auth/Source/RPCs/FIRVerifyAssertionRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.m index 25dc4cadea9..3a819d7a8ec 100644 --- a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionRequest.m +++ b/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionRequest.m @@ -120,9 +120,9 @@ - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable) value:_providerAccessToken]]; } - if (!_providerIDToken && !_providerAccessToken && !_requestURI) { + if (!_providerIDToken && !_providerAccessToken && !_pendingToken && !_requestURI) { [NSException raise:NSInvalidArgumentException - format:@"Either IDToken or accessToken must be supplied."]; + format:@"One of IDToken, accessToken, pendingToken, or requestURI must be supplied."]; } if (_providerOAuthTokenSecret) { diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.h similarity index 97% rename from Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.h index a75d0a259d4..295e2ffb153 100644 --- a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.h +++ b/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.h @@ -196,6 +196,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, strong, readonly, nullable) NSString *oauthAccessToken; +/** @property oauthSecretToken + @brief The secret for the OpenID OAuth extention. + */ +@property(nonatomic, readonly, nullable) NSString *oauthSecretToken; + /** @property pendingToken @brief The pending ID Token string. */ diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.m similarity index 98% rename from Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.m index e4792414cc7..a5f23d51deb 100644 --- a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionResponse.m +++ b/Firebase/Auth/Source/Backend/RPC/FIRVerifyAssertionResponse.m @@ -76,6 +76,7 @@ - (BOOL)setWithDictionary:(NSDictionary *)dictionary _oauthExpirationDate = [dictionary[@"oauthExpireIn"] isKindOfClass:[NSString class]] ? [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"oauthExpireIn"] doubleValue]] : nil; _oauthAccessToken = [dictionary[@"oauthAccessToken"] copy]; + _oauthSecretToken = [dictionary[@"oauthTokenSecret"] copy]; _pendingToken = [dictionary[@"pendingToken"] copy]; return YES; } diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyClientRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyClientRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyClientRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyClientRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyClientRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyClientResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyClientResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyClientResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyClientResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyClientResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyCustomTokenResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyCustomTokenResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPasswordRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyPasswordRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPasswordRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyPasswordRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPasswordResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyPasswordResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPasswordResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyPasswordResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyPasswordResponse.m diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberRequest.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberRequest.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.h diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberRequest.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberRequest.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberRequest.m diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberResponse.h b/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.h similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberResponse.h rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.h diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberResponse.m b/Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.m similarity index 100% rename from Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberResponse.m rename to Firebase/Auth/Source/Backend/RPC/FIRVerifyPhoneNumberResponse.m diff --git a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h deleted file mode 100644 index ccae93a7899..00000000000 --- a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** @protocol FIRAuthAppDelegateHandler - @brief The protocol to handle app delegate methods. - */ -@protocol FIRAuthAppDelegateHandler - -/** @fn setAPNSToken: - @brief Sets the APNs device token. - @param token The APNs device token. - */ -- (void)setAPNSToken:(NSData *)token; - -/** @fn handleAPNSTokenError: - @brief Handles APNs device token error. - @param error The APNs device token error. - */ -- (void)handleAPNSTokenError:(NSError *)error; - -/** @fn canHandleNotification: - @brief Checks whether the notification can be handled by the receiver, and handles it if so. - @param notification The notification in question, which will be consumed if returns @c YES. - @return Whether the notification can be (and already has been) handled by the receiver. - */ -- (BOOL)canHandleNotification:(nonnull NSDictionary *)notification; - -/** @fn canHandleURL: - @brief Checks whether the URL can be handled by the receiver, and handles it if so. - @param url The URL in question, which will be consumed if returns @c YES. - @return Whether the URL can be (and already has been) handled by the receiver. - */ -- (BOOL)canHandleURL:(nonnull NSURL *)url; - -@end - -/** @class FIRAuthAppDelegateProxy - @brief A manager for swizzling @c UIApplicationDelegate methods. - */ -@interface FIRAuthAppDelegateProxy : NSObject - -/** @fn initWithApplication - @brief Initialize the instance with the given @c UIApplication. - @returns An initialized instance, or @c nil if a proxy cannot be established. - @remarks This method should only be called from tests if called outside of this class. - */ -- (nullable instancetype)initWithApplication:(nullable UIApplication *)application - NS_DESIGNATED_INITIALIZER; - -/** @fn init - @brief Call @c sharedInstance to get an instance of this class. - */ -- (instancetype)init NS_UNAVAILABLE; - -/** @fn addHandler: - @brief Adds a handler for UIApplicationDelegate methods. - @param handler The handler to be added. - */ -- (void)addHandler:(__weak id)handler; - -/** @fn sharedInstance - @brief Gets the shared instance of this class. - @returns The shared instance of this class. - */ -+ (nullable instancetype)sharedInstance; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m deleted file mode 100644 index d97fedc6a69..00000000000 --- a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m +++ /dev/null @@ -1,412 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FIRAuthAppDelegateProxy.h" - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** @var kProxyEnabledBundleKey - @brief The key in application's bundle plist for whether or not proxy should be enabled. - @remarks This key is a shared constant with Analytics and FCM. - */ -static NSString *const kProxyEnabledBundleKey = @"FirebaseAppDelegateProxyEnabled"; - -/** @fn noop - @brief A function that does nothing. - @remarks This is used as the placeholder for unimplemented UApplicationDelegate methods, - because once we added a method there is no way to remove it from the class. - */ -#if !OBJC_OLD_DISPATCH_PROTOTYPES -static void noop(void) { -} -#else -static id noop(id object, SEL cmd, ...) { - return nil; -} -#endif - -/** @fn isIOS9orLater - @brief Checks whether the iOS version is 9 or later. - @returns Whether the iOS version is 9 or later. - */ -static BOOL isIOS9orLater() { -#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) - if (@available(iOS 9.0, *)) { - return YES; - } - return NO; -#else - // UIApplicationOpenURLOptionsAnnotationKey is only available on iOS 9+. - return &UIApplicationOpenURLOptionsAnnotationKey != NULL; -#endif -} - -@implementation FIRAuthAppDelegateProxy { - /** @var _appDelegate - @brief The application delegate whose method is being swizzled. - */ - id _appDelegate; - - /** @var _orginalImplementationsBySelector - @brief A map from selectors to original implementations that have been swizzled. - */ - NSMutableDictionary *_originalImplementationsBySelector; - - /** @var _handlers - @brief The array of weak pointers of `id`. - */ - NSPointerArray *_handlers; -} - -- (nullable instancetype)initWithApplication:(nullable UIApplication *)application { - self = [super init]; - if (self) { - id proxyEnabled = [[NSBundle mainBundle] objectForInfoDictionaryKey:kProxyEnabledBundleKey]; - if ([proxyEnabled isKindOfClass:[NSNumber class]] && !((NSNumber *)proxyEnabled).boolValue) { - return nil; - } - _appDelegate = application.delegate; - if (![_appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { - return nil; - } - _originalImplementationsBySelector = [[NSMutableDictionary alloc] init]; - _handlers = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory]; - - // Swizzle the methods. - __weak FIRAuthAppDelegateProxy *weakSelf = self; - SEL registerDeviceTokenSelector = - @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); - [self replaceSelector:registerDeviceTokenSelector - withBlock:^(id object, UIApplication* application, NSData *deviceToken) { - [weakSelf object:object - selector:registerDeviceTokenSelector - application:application - didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; - }]; - SEL failToRegisterRemoteNotificationSelector = - @selector(application:didFailToRegisterForRemoteNotificationsWithError:); - [self replaceSelector:failToRegisterRemoteNotificationSelector - withBlock:^(id object, UIApplication* application, NSError *error) { - [weakSelf object:object - selector:failToRegisterRemoteNotificationSelector - application:application - didFailToRegisterForRemoteNotificationsWithError:error]; - }]; - SEL receiveNotificationSelector = @selector(application:didReceiveRemoteNotification:); - SEL receiveNotificationWithHandlerSelector = - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - if ([_appDelegate respondsToSelector:receiveNotificationWithHandlerSelector] || - ![_appDelegate respondsToSelector:receiveNotificationSelector]) { - // Replace the modern selector which is available on iOS 7 and above. - [self replaceSelector:receiveNotificationWithHandlerSelector - withBlock:^(id object, UIApplication *application, NSDictionary *notification, - void (^completionHandler)(UIBackgroundFetchResult)) { - [weakSelf object:object - selector:receiveNotificationWithHandlerSelector - application:application - didReceiveRemoteNotification:notification - fetchCompletionHandler:completionHandler]; - }]; - } else { - // Replace the deprecated selector because this is the only one that the client app uses. - [self replaceSelector:receiveNotificationSelector - withBlock:^(id object, UIApplication *application, NSDictionary *notification) { - [weakSelf object:object - selector:receiveNotificationSelector - application:application - didReceiveRemoteNotification:notification]; - }]; - } - SEL openURLOptionsSelector = @selector(application:openURL:options:); - SEL openURLAnnotationSelector = @selector(application:openURL:sourceApplication:annotation:); - SEL handleOpenURLSelector = @selector(application:handleOpenURL:); - if (isIOS9orLater() && - ([_appDelegate respondsToSelector:openURLOptionsSelector] || - (![_appDelegate respondsToSelector:openURLAnnotationSelector] && - ![_appDelegate respondsToSelector:handleOpenURLSelector]))) { - // Replace the modern selector which is avaliable on iOS 9 and above because this is the one - // that the client app uses or the client app doesn't use any of them. - [self replaceSelector:openURLOptionsSelector - withBlock:^BOOL(id object, UIApplication *application, NSURL *url, - NSDictionary *options) { - return [weakSelf object:object - selector:openURLOptionsSelector - application:application - openURL:url - options:options]; - }]; - } else if ([_appDelegate respondsToSelector:openURLAnnotationSelector] || - ![_appDelegate respondsToSelector:handleOpenURLSelector]) { - // Replace the longer form of the deprecated selectors on iOS 8 and below because this is the - // one that the client app uses or the client app doesn't use either of the applicable ones. - [self replaceSelector:openURLAnnotationSelector - withBlock:^(id object, UIApplication *application, NSURL *url, - NSString *sourceApplication, id annotation) { - return [weakSelf object:object - selector:openURLAnnotationSelector - application:application - openURL:url - sourceApplication:sourceApplication - annotation:annotation]; - }]; - } else { - // Replace the shorter form of the deprecated selectors on iOS 8 and below because this is - // the only one that the client app uses. - [self replaceSelector:handleOpenURLSelector - withBlock:^(id object, UIApplication *application, NSURL *url) { - return [weakSelf object:object - selector:handleOpenURLSelector - application:application - handleOpenURL:url]; - }]; - } - // Reset the application delegate to clear the system cache that indicates whether each of the - // openURL: methods is implemented on the application delegate. - application.delegate = nil; - application.delegate = _appDelegate; - } - return self; -} - -- (void)dealloc { - for (NSValue *selector in _originalImplementationsBySelector) { - IMP implementation = _originalImplementationsBySelector[selector].pointerValue; - Method method = class_getInstanceMethod([_appDelegate class], selector.pointerValue); - imp_removeBlock(method_setImplementation(method, implementation)); - } -} - -- (void)addHandler:(__weak id)handler { - @synchronized (_handlers) { - [_handlers addPointer:(__bridge void *)handler]; - } -} - -+ (nullable instancetype)sharedInstance { - static dispatch_once_t onceToken; - static FIRAuthAppDelegateProxy *_Nullable sharedInstance; - // iOS App extensions should not call [UIApplication sharedApplication], even if UIApplication - // responds to it. - static Class applicationClass = nil; - dispatch_once(&onceToken, ^{ - if (![GULAppEnvironmentUtil isAppExtension]) { - Class cls = NSClassFromString(@"UIApplication"); - if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { - applicationClass = cls; - } - } - UIApplication *application = [applicationClass sharedApplication]; - sharedInstance = [[self alloc] initWithApplication:application]; - }); - return sharedInstance; -} - -#pragma mark - UIApplicationDelegate proxy methods. - -- (void)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - if (object == _appDelegate) { - for (id handler in [self handlers]) { - [handler setAPNSToken:deviceToken]; - } - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef void (*Implmentation)(id, SEL, UIApplication*, NSData *); - ((Implmentation)originalImplementation)(object, selector, application, deviceToken); - } -} - -- (void)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - if (object == _appDelegate) { - for (id handler in [self handlers]) { - [handler handleAPNSTokenError:error]; - } - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef void (*Implmentation)(id, SEL, UIApplication *, NSError *); - ((Implmentation)originalImplementation)(object, selector, application, error); - } -} - -- (void)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)notification - fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - if (object == _appDelegate) { - for (id handler in [self handlers]) { - if ([handler canHandleNotification:notification]) { - completionHandler(UIBackgroundFetchResultNoData); - return; - }; - } - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *, - void (^)(UIBackgroundFetchResult)); - ((Implmentation)originalImplementation)(object, selector, application, notification, - completionHandler); - } -} - -- (void)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)notification { - if (object == _appDelegate) { - for (id handler in [self handlers]) { - if ([handler canHandleNotification:notification]) { - return; - }; - } - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *); - ((Implmentation)originalImplementation)(object, selector, application, notification); - } -} - -- (BOOL)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - openURL:(NSURL *)url - options:(NSDictionary *)options { - if (object == _appDelegate && [self delegateCanHandleURL:url]) { - return YES; - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSDictionary *); - return ((Implmentation)originalImplementation)(object, selector, application, url, options); - } - return NO; -} - -- (BOOL)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - openURL:(NSURL *)url - sourceApplication:(NSString *)sourceApplication - annotation:(id)annotation { - if (object == _appDelegate && [self delegateCanHandleURL:url]) { - return YES; - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSString *, id); - return ((Implmentation)originalImplementation)(object, selector, application, url, - sourceApplication, annotation); - } - return NO; -} - -- (BOOL)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - handleOpenURL:(NSURL *)url { - if (object == _appDelegate && [self delegateCanHandleURL:url]) { - return YES; - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *); - return ((Implmentation)originalImplementation)(object, selector, application, url); - } - return NO; -} - -#pragma mark - Internal Methods - -/** @fn delegateCanHandleURL: - @brief Checks for whether any of the delegates can handle the URL. - @param url The URL in question. - @return Whether any of the delegate can handle the URL. - */ -- (BOOL)delegateCanHandleURL:(NSURL *)url { - for (id handler in [self handlers]) { - if ([handler canHandleURL:url]) { - return YES; - }; - } - return NO; -} - -/** @fn handlers - @brief Gets the list of handlers from `_handlers` safely. - */ -- (NSArray> *)handlers { - @synchronized (_handlers) { - NSMutableArray> *liveHandlers = - [[NSMutableArray> alloc] initWithCapacity:_handlers.count]; - for (__weak id handler in _handlers) { - if (handler) { - [liveHandlers addObject:handler]; - } - } - if (liveHandlers.count < _handlers.count) { - [_handlers compact]; - } - return liveHandlers; - } -} - -/** @fn replaceSelector:withBlock: - @brief replaces the implementation for a method of `_appDelegate` specified by a selector. - @param selector The selector for the method. - @param block The block as the new implementation of the method. - */ -- (void)replaceSelector:(SEL)selector withBlock:(id)block { - Method originalMethod = class_getInstanceMethod([_appDelegate class], selector); - IMP newImplementation = imp_implementationWithBlock(block); - IMP originalImplementation; - if (originalMethod) { - originalImplementation = method_setImplementation(originalMethod, newImplementation) ?: &noop; - } else { - // The original method was not implemented in the class, add it with the new implementation. - struct objc_method_description methodDescription = - protocol_getMethodDescription(@protocol(UIApplicationDelegate), selector, NO, YES); - class_addMethod([_appDelegate class], selector, newImplementation, methodDescription.types); - originalImplementation = &noop; - } - _originalImplementationsBySelector[[NSValue valueWithPointer:selector]] = - [NSValue valueWithPointer:originalImplementation]; -} - -/** @fn originalImplementationForSelector: - @brief Gets the original implementation for the given selector. - @param selector The selector for the method that has been replaced. - @return The original implementation if there was one. - */ -- (IMP)originalImplementationForSelector:(SEL)selector { - return _originalImplementationsBySelector[[NSValue valueWithPointer:selector]].pointerValue; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/Public/FIRActionCodeSettings.h b/Firebase/Auth/Source/Public/FIRActionCodeSettings.h index 788e0609907..5382b5bcb92 100644 --- a/Firebase/Auth/Source/Public/FIRActionCodeSettings.h +++ b/Firebase/Auth/Source/Public/FIRActionCodeSettings.h @@ -62,7 +62,7 @@ /** @property dynamicLinkDomain @brief The Firebase Dynamic Link domain used for out of band code flow. */ - @property (copy, nonatomic, nullable) NSString *dynamicLinkDomain; + @property(copy, nonatomic, nullable) NSString *dynamicLinkDomain; /** @fn setIOSBundleID @brief Sets the iOS bundle Id. diff --git a/Firebase/Auth/Source/Public/FIRAuth.h b/Firebase/Auth/Source/Public/FIRAuth.h index 50d8ca58be1..a5e7f752b8f 100644 --- a/Firebase/Auth/Source/Public/FIRAuth.h +++ b/Firebase/Auth/Source/Public/FIRAuth.h @@ -39,7 +39,8 @@ NS_ASSUME_NONNULL_BEGIN /** @typedef FIRUserUpdateCallback @brief The type of block invoked when a request to update the current user is completed. */ -typedef void (^FIRUserUpdateCallback)(NSError *_Nullable error) NS_SWIFT_NAME(UserUpdateCallback); +typedef void (^FIRUserUpdateCallback)(NSError *_Nullable error) + NS_SWIFT_NAME(UserUpdateCallback); /** @typedef FIRAuthStateDidChangeListenerHandle @brief The type of handle returned by `FIRAuth.addAuthStateDidChangeListener:`. @@ -289,12 +290,17 @@ NS_SWIFT_NAME(Auth) @remarks The string used to set this property must be a language code that follows BCP 47. */ -@property (nonatomic, copy, nullable) NSString *languageCode; +@property(nonatomic, copy, nullable) NSString *languageCode; /** @property settings @brief Contains settings related to the auth object. */ -@property (nonatomic, copy, nullable) FIRAuthSettings *settings; +@property(nonatomic, copy, nullable) FIRAuthSettings *settings; + +/** @property userAccessGroup + @brief The current user access group that the Auth instance is using. Default is nil. + */ +@property(readonly, nonatomic, copy, nullable) NSString *userAccessGroup; #if TARGET_OS_IOS /** @property APNSToken @@ -320,22 +326,13 @@ NS_SWIFT_NAME(Auth) - (void)updateCurrentUser:(FIRUser *)user completion:(nullable FIRUserUpdateCallback)completion; /** @fn fetchProvidersForEmail:completion: - @brief Fetches the list of IdPs that can be used for signing in with the provided email address. - Useful for an "identifier-first" sign-in flow. - - @param email The email address for which to obtain a list of identity providers. - @param completion Optionally; a block which is invoked when the list of providers for the - specified email address is ready or an error was encountered. Invoked asynchronously on the - main thread in the future. - - @remarks Possible error codes: - - + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - - @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + @brief Please use fetchSignInMethodsForEmail:completion: for Objective-C or + fetchSignInMethods(forEmail:completion:) for Swift instead. */ - (void)fetchProvidersForEmail:(NSString *)email - completion:(nullable FIRProviderQueryCallback)completion; + completion:(nullable FIRProviderQueryCallback)completion +DEPRECATED_MSG_ATTRIBUTE("Please use fetchSignInMethodsForEmail:completion: for Objective-C or " + "fetchSignInMethods(forEmail:completion:) for Swift instead."); /** @fn fetchSignInMethodsForEmail:completion: @brief Fetches the list of all sign-in methods previously used for the provided email address. @@ -404,68 +401,6 @@ NS_SWIFT_NAME(Auth) link:(NSString *)link completion:(nullable FIRAuthDataResultCallback)completion; -/** @fn signInAndRetrieveDataWithEmail:password:completion: - @brief Please use `signInWithEmail:password:completion:` for Objective-C or - `signIn(withEmail:password:completion:)` for Swift instead. - - @param email The user's email address. - @param password The user's password. - @param completion Optionally; a block which is invoked when the sign in flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - */ -- (void)signInAndRetrieveDataWithEmail:(NSString *)email - password:(NSString *)password - completion:(nullable FIRAuthDataResultCallback)completion - DEPRECATED_MSG_ATTRIBUTE( - "Please use signInWithEmail:password:completion: for" - " Objective-C or signIn(withEmail:password:completion:) for" - " Swift instead."); - -/** @fn signInWithCredential:completion: - @brief Please use `signInAndRetrieveDataWithCredential:completion:` for Objective-C or - `signInAndRetrieveData(with:completion:)` for swift instead - - @param credential The credential supplied by the IdP. - @param completion Optionally; a block which is invoked when the sign in flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `FIRAuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. - This could happen if it has expired or it is malformed. - + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that accounts - with the identity provider represented by the credential are not enabled. - Enable them in the Auth section of the Firebase console. - + `FIRAuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted - by the credential (e.g. the email in a Facebook access token) is already in use by an - existing account, that cannot be authenticated with this sign-in method. Call - fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of - the sign-in providers returned. This error will only be thrown if the "One account per - email address" setting is enabled in the Firebase console, under Auth settings. - + `FIRAuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `FIRAuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an - incorrect password, if credential is of the type EmailPasswordAuthCredential. - + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - + `FIRAuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was - created with an empty verification ID. - + `FIRAuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential - was created with an empty verification code. - + `FIRAuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential - was created with an invalid verification Code. - + `FIRAuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was - created with an invalid verification ID. - + `FIRAuthErrorCodeSessionExpired` - Indicates that the SMS code has expired. - - - - @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods - */ -- (void)signInWithCredential:(FIRAuthCredential *)credential - completion:(nullable FIRAuthResultCallback)completion DEPRECATED_MSG_ATTRIBUTE( - "Please use signInAndRetrieveDataWithCredential:completion:" - " for Objective-C or signInAndRetrieveData(with:completion:)" - " for Swift instead."); - /** @fn signInWithProvider:UIDelegate:completion: @brief Signs in using the provided auth provider instance. @@ -487,7 +422,7 @@ NS_SWIFT_NAME(Auth)
  • @c FIRAuthErrorCodeWebNetworkRequestFailed - Indicates that a network request within a SFSafariViewController or UIWebview failed.
  • -
  • @c FIRAuthErrorCodeWebInternalError - Indicates that an internal error occured within a +
  • @c FIRAuthErrorCodeWebInternalError - Indicates that an internal error occurred within a SFSafariViewController or UIWebview.
  • @c FIRAuthErrorCodeWebSignInUserInteractionFailure - Indicates a general failure during @@ -515,6 +450,15 @@ NS_SWIFT_NAME(Auth) completion:(nullable FIRAuthDataResultCallback)completion; /** @fn signInAndRetrieveDataWithCredential:completion: + @brief Please use signInWithCredential:completion: for Objective-C or " + "signIn(with:completion:) for Swift instead. + */ +- (void)signInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion +DEPRECATED_MSG_ATTRIBUTE("Please use signInWithCredential:completion: for Objective-C or " + "signIn(with:completion:) for Swift instead."); + +/** @fn signInWithCredential:completion: @brief Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional identity provider data. @@ -525,37 +469,35 @@ NS_SWIFT_NAME(Auth) @remarks Possible error codes: - + `FIRAuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. - This could happen if it has expired or it is malformed. - + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that accounts - with the identity provider represented by the credential are not enabled. - Enable them in the Auth section of the Firebase console. - + `FIRAuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted - by the credential (e.g. the email in a Facebook access token) is already in use by an - existing account, that cannot be authenticated with this sign-in method. Call - fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of - the sign-in providers returned. This error will only be thrown if the "One account per - email address" setting is enabled in the Firebase console, under Auth settings. - + `FIRAuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `FIRAuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an - incorrect password, if credential is of the type EmailPasswordAuthCredential. - + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - + `FIRAuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was - created with an empty verification ID. - + `FIRAuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential - was created with an empty verification code. - + `FIRAuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential - was created with an invalid verification Code. - + `FIRAuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was - created with an invalid verification ID. - + `FIRAuthErrorCodeSessionExpired` - Indicates that the SMS code has expired. - - + + `FIRAuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. + This could happen if it has expired or it is malformed. + + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that accounts + with the identity provider represented by the credential are not enabled. + Enable them in the Auth section of the Firebase console. + + `FIRAuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted + by the credential (e.g. the email in a Facebook access token) is already in use by an + existing account, that cannot be authenticated with this sign-in method. Call + fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of + the sign-in providers returned. This error will only be thrown if the "One account per + email address" setting is enabled in the Firebase console, under Auth settings. + + `FIRAuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + + `FIRAuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an + incorrect password, if credential is of the type EmailPasswordAuthCredential. + + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + + `FIRAuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was + created with an empty verification ID. + + `FIRAuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential + was created with an empty verification code. + + `FIRAuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential + was created with an invalid verification Code. + + `FIRAuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was + created with an invalid verification ID. + + `FIRAuthErrorCodeSessionExpired` - Indicates that the SMS code has expired. @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods - */ -- (void)signInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential - completion:(nullable FIRAuthDataResultCallback)completion; +*/ +- (void)signInWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion; /** @fn signInAnonymouslyWithCompletion: @brief Asynchronously creates and becomes an anonymous user. @@ -574,17 +516,6 @@ NS_SWIFT_NAME(Auth) */ - (void)signInAnonymouslyWithCompletion:(nullable FIRAuthDataResultCallback)completion; -/** @fn signInAnonymouslyAndRetrieveDataWithCompletion: - @brief `Please use sign `signInAnonymouslyWithCompletion:` for Objective-C or - `signInAnonymously(Completion:)` for Swift instead. - @param completion Optionally; a block which is invoked when the sign in finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - */ -- (void)signInAnonymouslyAndRetrieveDataWithCompletion: - (nullable FIRAuthDataResultCallback)completion - DEPRECATED_MSG_ATTRIBUTE("Please use signInAnonymouslyWithCompletion: for Objective-C or" - " signInAnonymously(Completion:) for swift instead."); - /** @fn signInWithCustomToken:completion: @brief Asynchronously signs in to Firebase with the given Auth token. @@ -599,28 +530,11 @@ NS_SWIFT_NAME(Auth) + `FIRAuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key belong to different projects. - - @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. */ - (void)signInWithCustomToken:(NSString *)token completion:(nullable FIRAuthDataResultCallback)completion; -/** @fn signInAndRetrieveDataWithCustomToken:completion: - @brief Please use `signInWithCustomToken:completion:` or `signIn(withCustomToken:completion:)` - for Swift instead. - - @param token A self-signed custom auth token. - @param completion Optionally; a block which is invoked when the sign in finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - */ -- (void)signInAndRetrieveDataWithCustomToken:(NSString *)token - completion:(nullable FIRAuthDataResultCallback)completion - DEPRECATED_MSG_ATTRIBUTE( - "Please use signInWithCustomToken:completion:" - "for Objective-C or signIn(withCustomToken:completion:) for" - " Swift instead."); - /** @fn createUserWithEmail:password:completion: @brief Creates and, on success, signs in a user with the given email address and password. @@ -647,23 +561,6 @@ NS_SWIFT_NAME(Auth) password:(NSString *)password completion:(nullable FIRAuthDataResultCallback)completion; -/** @fn createUserAndRetrieveDataWithEmail:password:completion: - @brief Please use `createUserAndRetrieveDataWithEmail:password:completion:` or - `createUser(withEmail:password:completion:)` for Swift instead. - - @param email The user's email address. - @param password The user's desired password. - @param completion Optionally; a block which is invoked when the sign up flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - */ -- (void)createUserAndRetrieveDataWithEmail:(NSString *)email - password:(NSString *)password - completion:(nullable FIRAuthDataResultCallback)completion - DEPRECATED_MSG_ATTRIBUTE( - "Please use createUserWithEmail:password:completion: for" - " Objective-C or createUser(withEmail:password:completion:)" - " for Swift instead."); - /** @fn confirmPasswordResetWithCode:newPassword:completion: @brief Resets the password given a code sent to the user outside of the app and a new password for the user. @@ -909,6 +806,21 @@ NS_SWIFT_NAME(Auth) #endif // TARGET_OS_IOS +#pragma mark - User sharing + +/** @fn useUserAccessGroup:error: + @brief Switch userAccessGroup and current user to the given accessGroup and the user stored in + it. + */ +- (BOOL)useUserAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn getStoredUserForAccessGroup:error: + @brief Get the stored user in the given accessGroup. + */ +- (FIRUser *)getStoredUserForAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError; + @end NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/Public/FIRAuthDataResult.h b/Firebase/Auth/Source/Public/FIRAuthDataResult.h index bc4fa4a2a02..f463dd7a68e 100644 --- a/Firebase/Auth/Source/Public/FIRAuthDataResult.h +++ b/Firebase/Auth/Source/Public/FIRAuthDataResult.h @@ -17,6 +17,7 @@ #import @class FIRAdditionalUserInfo; +@class FIROAuthCredential; @class FIRUser; NS_ASSUME_NONNULL_BEGIN @@ -37,7 +38,14 @@ NS_SWIFT_NAME(AuthDataResult) /** @property user @brief The signed in user. */ -@property(nonatomic, readonly) FIRUser *user; +@property(nonatomic, readonly, nullable) FIRUser *user; + +/** @property credential + @brief The updated OAuth credential after the the sign-in, link and reauthenticate action. + @detial This property is for OAuth sign in only. + */ +@property(nonatomic, readonly, nullable) FIROAuthCredential *credential; + /** @property additionalUserInfo @brief If available contains the additional IdP specific information about signed in user. diff --git a/Firebase/Auth/Source/Public/FIRAuthErrors.h b/Firebase/Auth/Source/Public/FIRAuthErrors.h index 656043e5fd0..63cd3432b60 100644 --- a/Firebase/Auth/Source/Public/FIRAuthErrors.h +++ b/Firebase/Auth/Source/Public/FIRAuthErrors.h @@ -44,20 +44,6 @@ NS_SWIFT_NAME(AuthErrors) */ extern NSString *const FIRAuthErrorDomain NS_SWIFT_NAME(AuthErrorDomain); -/** - @brief Please use `FIRAuthErrorUserInfoUpdatedCredentialKey` for Objective C or - `AuthErrorUserInfoUpdatedCredentialKey` for Swift instead. - */ -extern NSString *const FIRAuthUpdatedCredentialKey - NS_SWIFT_NAME(AuthUpdatedCredentialKey) __attribute__((deprecated)); - -/** - @brief Please use `FIRAuthErrorUserInfoNameKey` for Objective C or - `AuthErrorUserInfoNameKey` for Swift instead. - */ -extern NSString *const FIRAuthErrorNameKey - NS_SWIFT_NAME(AuthErrorNameKey) __attribute__((deprecated)); - /** @brief The name of the key for the error short string of an error code. */ @@ -334,6 +320,11 @@ typedef NS_ENUM(NSInteger, FIRAuthErrorCode) { */ FIRAuthErrorCodeNullUser = 17067, + /** + * Represents the error code for when the given provider id for a web operation is invalid. + */ + FIRAuthErrorCodeInvalidProviderID = 17071, + /** Indicates that the Firebase Dynamic Link domain used is either not configured or is unauthorized for the current project. */ diff --git a/Firebase/Auth/Source/Public/FIRAuthSettings.h b/Firebase/Auth/Source/Public/FIRAuthSettings.h index 05fc60199c8..4ac7ce87621 100644 --- a/Firebase/Auth/Source/Public/FIRAuthSettings.h +++ b/Firebase/Auth/Source/Public/FIRAuthSettings.h @@ -21,12 +21,13 @@ NS_ASSUME_NONNULL_BEGIN /** @class FIRAuthSettings @brief Determines settings related to an auth object. */ +NS_SWIFT_NAME(AuthSettings) @interface FIRAuthSettings : NSObject /** @property appVerificationDisabledForTesting @brief Flag to determine whether app verification should be disabled for testing or not. */ -@property (nonatomic, assign, getter=isAppVerificationDisabledForTesting) BOOL +@property(nonatomic, assign, getter=isAppVerificationDisabledForTesting) BOOL appVerificationDisabledForTesting; @end diff --git a/Firebase/Auth/Source/Public/FIRAuthTokenResult.h b/Firebase/Auth/Source/Public/FIRAuthTokenResult.h index 9e0028d7b70..515aa60d2cc 100644 --- a/Firebase/Auth/Source/Public/FIRAuthTokenResult.h +++ b/Firebase/Auth/Source/Public/FIRAuthTokenResult.h @@ -28,36 +28,36 @@ NS_SWIFT_NAME(AuthTokenResult) /** @property token @brief Stores the JWT string of the ID token. */ -@property (nonatomic, readonly) NSString *token; +@property(nonatomic, readonly) NSString *token; /** @property expirationDate @brief Stores the ID token's expiration date. */ -@property (nonatomic, readonly) NSDate *expirationDate; +@property(nonatomic, readonly) NSDate *expirationDate; /** @property authDate @brief Stores the ID token's authentication date. @remarks This is the date the user was signed in and NOT the date the token was refreshed. */ -@property (nonatomic, readonly) NSDate *authDate; +@property(nonatomic, readonly) NSDate *authDate; /** @property issuedAtDate @brief Stores the date that the ID token was issued. @remarks This is the date last refreshed and NOT the last authentication date. */ -@property (nonatomic, readonly) NSDate *issuedAtDate; +@property(nonatomic, readonly) NSDate *issuedAtDate; /** @property signInProvider @brief Stores sign-in provider through which the token was obtained. @remarks This does not necessarily map to provider IDs. */ -@property (nonatomic, readonly) NSString *signInProvider; +@property(nonatomic, readonly) NSString *signInProvider; /** @property claims @brief Stores the entire payload of claims found on the ID token. This includes the standard reserved claims as well as custom claims set by the developer via the Admin SDK. */ -@property (nonatomic, readonly) NSDictionary *claims; +@property(nonatomic, readonly) NSDictionary *claims; diff --git a/Firebase/Auth/Source/Public/FIREmailAuthProvider.h b/Firebase/Auth/Source/Public/FIREmailAuthProvider.h index b6375bd2a69..bb8f85dc2f4 100644 --- a/Firebase/Auth/Source/Public/FIREmailAuthProvider.h +++ b/Firebase/Auth/Source/Public/FIREmailAuthProvider.h @@ -33,13 +33,7 @@ extern NSString *const FIREmailLinkAuthSignInMethod NS_SWIFT_NAME(EmailLinkAuthS /** @brief A string constant identifying the email & password sign-in method. */ -extern NSString *const FIREmailPasswordAuthSignInMethod - NS_SWIFT_NAME(EmailPasswordAuthSignInMethod); - -/** - @brief Please use `FIREmailAuthProviderID` for Objective-C or `EmailAuthProviderID` for Swift instead. - */ -extern NSString *const FIREmailPasswordAuthProviderID __attribute__((deprecated)); +extern NSString *const FIREmailPasswordAuthSignInMethod NS_SWIFT_NAME(EmailPasswordAuthSignInMethod); /** @class FIREmailAuthProvider @brief A concrete implementation of `FIRAuthProvider` for Email & Password Sign In. @@ -47,12 +41,6 @@ extern NSString *const FIREmailPasswordAuthProviderID __attribute__((deprecated) NS_SWIFT_NAME(EmailAuthProvider) @interface FIREmailAuthProvider : NSObject -/** @typedef FIREmailPasswordAuthProvider - @brief Please use `FIREmailAuthProvider` instead. - */ -typedef FIREmailAuthProvider FIREmailPasswordAuthProvider __attribute__((deprecated)); - - /** @fn credentialWithEmail:password: @brief Creates an `FIRAuthCredential` for an email & password sign in. diff --git a/Firebase/Auth/Source/Public/FIROAuthCredential.h b/Firebase/Auth/Source/Public/FIROAuthCredential.h index 43b1d8111f2..94abe4f22b4 100644 --- a/Firebase/Auth/Source/Public/FIROAuthCredential.h +++ b/Firebase/Auth/Source/Public/FIROAuthCredential.h @@ -36,10 +36,12 @@ NS_SWIFT_NAME(OAuthCredential) */ @property(nonatomic, readonly, nullable) NSString *accessToken; -/** @property pendingToken - @brief The pending token used when completing the headful-lite flow. +/** @property secret + @brief The secret associated with this credential. This will be nil for OAuth 2.0 providers. + @detail OAuthCredential already exposes a providerId getter. This will help the developer + determine whether an access token/secret pair is needed. */ -@property(nonatomic, readonly, nullable) NSString *pendingToken; +@property(nonatomic, readonly, nullable) NSString *secret; /** @fn init @brief This class is not supposed to be instantiated directly. diff --git a/Firebase/Auth/Source/Public/FIROAuthProvider.h b/Firebase/Auth/Source/Public/FIROAuthProvider.h index 29531012108..57635e94e20 100644 --- a/Firebase/Auth/Source/Public/FIROAuthProvider.h +++ b/Firebase/Auth/Source/Public/FIROAuthProvider.h @@ -23,18 +23,6 @@ NS_ASSUME_NONNULL_BEGIN - -/** - @brief A string constant identifying the Microsoft identity provider. - */ -extern NSString *const FIRMicrosoftAuthProviderID NS_SWIFT_NAME(MicrosoftAuthProviderID); - -/** - @brief A string constant identifying the Yahoo identity provider. - */ -extern NSString *const FIRYahooAuthProviderID NS_SWIFT_NAME(YahooAuthProviderID); - - /** @class FIROAuthProvider @brief A concrete implementation of `FIRAuthProvider` for generic OAuth Providers. */ @@ -72,22 +60,6 @@ NS_SWIFT_NAME(OAuthProvider) */ + (FIROAuthProvider *)providerWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth; -/** @fn credentialWithProviderID:IDToken:accessToken: - @brief Creates an `FIRAuthCredential` for that OAuth 2 provider identified by providerID, ID - token and access token. - - @param providerID The provider ID associated with the Auth credential being created. - @param IDToken The IDToken associated with the Auth credential being created. - @param accessToken The accessstoken associated with the Auth credential be created, if - available. - @param pendingToken The pending token used when completing the headful-lite flow. - @return A FIRAuthCredential for the specified provider ID, ID token and access token. - */ -+ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID - IDToken:(NSString *)IDToken - accessToken:(nullable NSString *)accessToken - pendingToken:(nullable NSString *)pendingToken; - /** @fn credentialWithProviderID:IDToken:accessToken: @brief Creates an `FIRAuthCredential` for that OAuth 2 provider identified by providerID, ID token and access token. diff --git a/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h b/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h index a0d1166f1c8..0f1b28d7377 100644 --- a/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h +++ b/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h @@ -24,13 +24,11 @@ NS_ASSUME_NONNULL_BEGIN @brief A string constant identifying the Twitter identity provider. */ extern NSString *const FIRTwitterAuthProviderID NS_SWIFT_NAME(TwitterAuthProviderID); - /** @brief A string constant identifying the Twitter sign-in method. */ extern NSString *const _Nonnull FIRTwitterAuthSignInMethod NS_SWIFT_NAME(TwitterAuthSignInMethod); - /** @class FIRTwitterAuthProvider @brief Utility class for constructing Twitter credentials. */ diff --git a/Firebase/Auth/Source/Public/FIRUser.h b/Firebase/Auth/Source/Public/FIRUser.h index cc5d0a00120..5055ea64531 100644 --- a/Firebase/Auth/Source/Public/FIRUser.h +++ b/Firebase/Auth/Source/Public/FIRUser.h @@ -221,18 +221,6 @@ NS_SWIFT_NAME(User) - (void)reloadWithCompletion:(nullable FIRUserProfileChangeCallback)completion; /** @fn reauthenticateWithCredential:completion: - @brief Please use reauthenticateAndRetrieveDataWithCredential:completion: for Objective-C or - reauthenticateAndRetrieveData(WithCredential:completion:) for Swift instead. - */ -- (void)reauthenticateWithCredential:(FIRAuthCredential *)credential - completion:(nullable FIRUserProfileChangeCallback)completion - DEPRECATED_MSG_ATTRIBUTE( "Please use" - " reauthenticateAndRetrieveDataWithCredential:completion: for" - " Objective-C or" - " reauthenticateAndRetrieveData(WithCredential:completion:)" - " for Swift instead."); - -/** @fn reauthenticateAndRetrieveDataWithCredential:completion: @brief Renews the user's authentication tokens by validating a fresh set of credentials supplied by the user and returns additional identity provider data. @@ -268,8 +256,18 @@ NS_SWIFT_NAME(User) @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. */ -- (void)reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *) credential - completion:(nullable FIRAuthDataResultCallback) completion; +- (void)reauthenticateWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion; + +/** @fn reauthenticateAndRetrieveDataWithCredential:completion: + @brief Please use linkWithCredential:completion: for Objective-C + or link(withCredential:completion:) for Swift instead. + */ +- (void)reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion +DEPRECATED_MSG_ATTRIBUTE( "Please use reauthenticateWithCredential:completion: for" + " Objective-C or reauthenticate(withCredential:completion:)" + " for Swift instead."); /** @fn getIDTokenResultWithCompletion: @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. @@ -326,20 +324,18 @@ NS_SWIFT_NAME(User) - (void)getIDTokenForcingRefresh:(BOOL)forceRefresh completion:(nullable FIRAuthTokenCallback)completion; -/** @fn linkWithCredential:completion: - @brief Please use linkAndRetrieveDataWithCredential:completion: for Objective-C or - linkAndRetrieveData(WithCredential:completion:) for Swift instead. +/** @fn linkAndRetrieveDataWithCredential:completion: + @brief Please use linkWithCredential:completion: for Objective-C + or link(withCredential:completion:) for Swift instead. */ -- (void)linkWithCredential:(FIRAuthCredential *)credential - completion:(nullable FIRAuthResultCallback)completion DEPRECATED_MSG_ATTRIBUTE( - "Please use linkAndRetrieveDataWithCredential:completion: for" - " Objective-C or" - " linkAndRetrieveData(WithCredential:completion:) for" - " Swift instead."); +- (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion +DEPRECATED_MSG_ATTRIBUTE("Please use linkWithCredential:completion: for Objective-C " + "or link(withCredential:completion:) for Swift instead."); -/** @fn linkAndRetrieveDataWithCredential:completion: +/** @fn linkWithCredential:completion: @brief Associates a user account from a third-party identity provider with this user and - returns additional identity provider data. + returns additional identity provider data. @param credential The credential for the identity provider. @param completion Optionally; the block invoked when the unlinking is complete, or fails. @@ -350,19 +346,18 @@ NS_SWIFT_NAME(User) + `FIRAuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a type already linked to this account. + `FIRAuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a - credential - that has already been linked with a different Firebase account. + credential that has already been linked with a different Firebase account. + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity provider represented by the credential are not enabled. Enable them in the Auth section of the Firebase console. @remarks This method may also return error codes associated with updateEmail:completion: and - updatePassword:completion: on FIRUser. + updatePassword:completion: on FIRUser. @remarks See `FIRAuthErrors` for a list of error codes that are common to all FIRUser methods. */ -- (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *) credential - completion:(nullable FIRAuthDataResultCallback) completion; +- (void)linkWithCredential:(FIRAuthCredential *)credential + completion:(nullable FIRAuthDataResultCallback)completion; /** @fn unlinkFromProvider:completion: @brief Disassociates a user account from a third-party identity provider with this user. diff --git a/Firebase/Auth/Source/Public/FIRUserMetadata.h b/Firebase/Auth/Source/Public/FIRUserMetadata.h index 25331718d9e..023c205755b 100644 --- a/Firebase/Auth/Source/Public/FIRUserMetadata.h +++ b/Firebase/Auth/Source/Public/FIRUserMetadata.h @@ -27,12 +27,12 @@ NS_SWIFT_NAME(UserMetadata) /** @property lastSignInDate @brief Stores the last sign in date for the corresponding Firebase user. */ -@property (copy, nonatomic, readonly, nullable) NSDate *lastSignInDate; +@property(copy, nonatomic, readonly, nullable) NSDate *lastSignInDate; /** @property creationDate @brief Stores the creation date for the corresponding Firebase user. */ -@property (copy, nonatomic, readonly, nullable) NSDate *creationDate; +@property(copy, nonatomic, readonly, nullable) NSDate *creationDate; /** @fn init @brief This class should not be initialized manually, an instance of this class can be obtained diff --git a/Firebase/Auth/Source/FIRAuthKeychain.h b/Firebase/Auth/Source/Storage/FIRAuthKeychain.h similarity index 68% rename from Firebase/Auth/Source/FIRAuthKeychain.h rename to Firebase/Auth/Source/Storage/FIRAuthKeychain.h index c52e26aafa5..f57d2730650 100644 --- a/Firebase/Auth/Source/FIRAuthKeychain.h +++ b/Firebase/Auth/Source/Storage/FIRAuthKeychain.h @@ -65,6 +65,33 @@ NS_ASSUME_NONNULL_BEGIN @brief The utility class to manipulate data in iOS Keychain. */ @interface FIRAuthKeychain : NSObject + +/** @fn getItemWithQuery:error: + @brief Get the item from keychain by given query. + @param query The query to query the keychain. + @param outError The address to store any error that occurs during the process, if not nil. + @return The item of the given query. nil if not exsit. + */ +- (NSData *)getItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; + +/** @fn setItem:withQuery:error: + @brief Set the item into keychain with given query. + @param item The item to be added into keychain. + @param query The query to query the keychain. + @param outError The address to store any error that occurs during the process, if not nil. + @return Whether the operation succeed. + */ +- (BOOL)setItem:(NSData *)item withQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn getItemWithQuery:error: + @brief Remove the item with given queryfrom keychain. + @param query The query to query the keychain. + @param outError The address to store any error that occurs during the process, if not nil. + @return Whether the operation succeed. + */ +- (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError; + @end NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRAuthKeychain.m b/Firebase/Auth/Source/Storage/FIRAuthKeychain.m similarity index 73% rename from Firebase/Auth/Source/FIRAuthKeychain.m rename to Firebase/Auth/Source/Storage/FIRAuthKeychain.m index d196244441e..9c264742818 100644 --- a/Firebase/Auth/Source/FIRAuthKeychain.m +++ b/Firebase/Auth/Source/Storage/FIRAuthKeychain.m @@ -21,17 +21,6 @@ #import "FIRAuthErrorUtils.h" #import "FIRAuthUserDefaultsStorage.h" -#if FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE -#import - -/** @var kOSVersionMatcherForUsingUserDefaults - @brief The regular expression to match all OS versions that @c FIRAuthUserDefaultsStorage is - used instead if available. - */ -static NSString *const kOSVersionMatcherForUsingUserDefaults = @"^10\\.[01](\\..*)?$"; - -#endif // FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE - /** @var kAccountPrefix @brief The prefix string for keychain item account attribute before the key. @remarks A number "1" is encoded in the prefix in case we need to upgrade the scheme in future. @@ -56,19 +45,6 @@ @implementation FIRAuthKeychain { - (id)initWithService:(NSString *)service { -#if FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE - - NSString *OSVersion = [UIDevice currentDevice].systemVersion; - NSRegularExpression *regex = - [NSRegularExpression regularExpressionWithPattern:kOSVersionMatcherForUsingUserDefaults - options:0 - error:NULL]; - if ([regex numberOfMatchesInString:OSVersion options:0 range:NSMakeRange(0, OSVersion.length)]) { - return (id)[[FIRAuthUserDefaultsStorage alloc] initWithService:service]; - } - -#endif // FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE - self = [super init]; if (self) { _service = [service copy]; @@ -141,7 +117,7 @@ - (BOOL)removeDataForKey:(NSString *)key error:(NSError **_Nullable)error { return YES; } -#pragma mark - Private +#pragma mark - Private methods for non-sharing keychain operations - (NSData *)itemWithQuery:(NSDictionary *)query error:(NSError **_Nullable)error { NSMutableDictionary *returningQuery = [query mutableCopy]; @@ -156,7 +132,7 @@ - (NSData *)itemWithQuery:(NSDictionary *)query error:(NSError **_Nullable)error (CFTypeRef *)&result); if (status == noErr && result != NULL) { - NSArray *items = (__bridge_transfer NSArray *)result; + NSArray *items = (__bridge_transfer NSArray *)result; if (items.count != 1) { if (error) { *error = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemCopyMatching" @@ -255,6 +231,96 @@ - (NSDictionary *)legacyGenericPasswordQueryWithKey:(NSString *)key { }; } +#pragma mark - Private methods for shared keychain operations + +- (NSData *)getItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError { + NSMutableDictionary *mutableQuery = [query mutableCopy]; + + mutableQuery[(__bridge id)kSecReturnData] = @YES; + mutableQuery[(__bridge id)kSecReturnAttributes] = @YES; + mutableQuery[(__bridge id)kSecMatchLimit] = @2; + + CFArrayRef result = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)mutableQuery, + (CFTypeRef *)&result); + + if (status == noErr && result != NULL) { + NSArray *items = (__bridge_transfer NSArray *)result; + if (items.count != 1) { + if (outError) { + *outError = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemCopyMatching" + status:status]; + } + return nil; + } + + if (outError) { + *outError = nil; + } + NSDictionary *item = items[0]; + return item[(__bridge id)kSecValueData]; + } + + if (status == errSecItemNotFound) { + if (outError) { + *outError = nil; + } + } else { + if (outError) { + *outError = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemCopyMatching" status:status]; + } + } + return nil; +} + +- (BOOL)setItem:(NSData *)item + withQuery:(NSDictionary *)query + error:(NSError *_Nullable *_Nullable)outError { + NSData *existingItem = [self getItemWithQuery:query error:outError]; + if (outError && *outError) { + return NO; + } + + OSStatus status; + if (!existingItem) { + NSMutableDictionary *queryWithItem = [query mutableCopy]; + [queryWithItem setObject:item forKey:(__bridge id)kSecValueData]; + status = SecItemAdd((__bridge CFDictionaryRef)queryWithItem, NULL); + } else { + NSDictionary *attributes = @{(__bridge id)kSecValueData: item}; + status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes); + } + + if (status == noErr) { + if (outError) { + *outError = nil; + } + return YES; + } + + NSString *function = existingItem ? @"SecItemUpdate" : @"SecItemAdd"; + if (outError) { + *outError = [FIRAuthErrorUtils keychainErrorWithFunction:function status:status]; + } + return NO; +} + +- (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError { + OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); + + if (status == noErr || status == errSecItemNotFound) { + if (outError) { + *outError = nil; + } + return YES; + } + + if (outError) { + *outError = [FIRAuthErrorUtils keychainErrorWithFunction:@"SecItemDelete" status:status]; + } + return NO; +} + @end NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRAuthUserDefaultsStorage.h b/Firebase/Auth/Source/Storage/FIRAuthUserDefaultsStorage.h similarity index 78% rename from Firebase/Auth/Source/FIRAuthUserDefaultsStorage.h rename to Firebase/Auth/Source/Storage/FIRAuthUserDefaultsStorage.h index 13774ab97ba..7fa2bec59ff 100644 --- a/Firebase/Auth/Source/FIRAuthUserDefaultsStorage.h +++ b/Firebase/Auth/Source/Storage/FIRAuthUserDefaultsStorage.h @@ -16,15 +16,6 @@ #import -// This class is only available in the simulator. -#if TARGET_OS_SIMULATOR -#ifndef FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE -#define FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE 1 -#endif -#endif - -#if FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE - #import "FIRAuthKeychain.h" NS_ASSUME_NONNULL_BEGIN @@ -43,5 +34,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END - -#endif // FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE diff --git a/Firebase/Auth/Source/FIRAuthUserDefaultsStorage.m b/Firebase/Auth/Source/Storage/FIRAuthUserDefaultsStorage.m similarity index 96% rename from Firebase/Auth/Source/FIRAuthUserDefaultsStorage.m rename to Firebase/Auth/Source/Storage/FIRAuthUserDefaultsStorage.m index d9f012a96d3..52806d9488f 100644 --- a/Firebase/Auth/Source/FIRAuthUserDefaultsStorage.m +++ b/Firebase/Auth/Source/Storage/FIRAuthUserDefaultsStorage.m @@ -16,8 +16,6 @@ #import "FIRAuthUserDefaultsStorage.h" -#if FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE - NS_ASSUME_NONNULL_BEGIN static NSString *const kPersistentDomainNamePrefix = @"com.google.Firebase.Auth."; @@ -74,5 +72,3 @@ - (void)clear { @end NS_ASSUME_NONNULL_END - -#endif // FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE diff --git a/Firebase/Auth/Source/FIRAuthAPNSToken.h b/Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthAPNSToken.h rename to Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.h diff --git a/Firebase/Auth/Source/FIRAuthAPNSToken.m b/Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthAPNSToken.m rename to Firebase/Auth/Source/SystemService/FIRAuthAPNSToken.m diff --git a/Firebase/Auth/Source/FIRAuthAPNSTokenManager.h b/Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthAPNSTokenManager.h rename to Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.h diff --git a/Firebase/Auth/Source/FIRAuthAPNSTokenManager.m b/Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthAPNSTokenManager.m rename to Firebase/Auth/Source/SystemService/FIRAuthAPNSTokenManager.m diff --git a/Firebase/Auth/Source/FIRAuthAppCredential.h b/Firebase/Auth/Source/SystemService/FIRAuthAppCredential.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthAppCredential.h rename to Firebase/Auth/Source/SystemService/FIRAuthAppCredential.h diff --git a/Firebase/Auth/Source/FIRAuthAppCredential.m b/Firebase/Auth/Source/SystemService/FIRAuthAppCredential.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthAppCredential.m rename to Firebase/Auth/Source/SystemService/FIRAuthAppCredential.m diff --git a/Firebase/Auth/Source/FIRAuthAppCredentialManager.h b/Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthAppCredentialManager.h rename to Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.h diff --git a/Firebase/Auth/Source/FIRAuthAppCredentialManager.m b/Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthAppCredentialManager.m rename to Firebase/Auth/Source/SystemService/FIRAuthAppCredentialManager.m diff --git a/Firebase/Auth/Source/FIRAuthNotificationManager.h b/Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthNotificationManager.h rename to Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.h diff --git a/Firebase/Auth/Source/FIRAuthNotificationManager.m b/Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthNotificationManager.m rename to Firebase/Auth/Source/SystemService/FIRAuthNotificationManager.m diff --git a/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.h b/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.h new file mode 100644 index 00000000000..cbb6d3edce2 --- /dev/null +++ b/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.h @@ -0,0 +1,99 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRUser.h" +#import "FIRAuthKeychain.h" +#import "FIRAuthUserDefaultsStorage.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthStoredUserManager : NSObject + +/** @property keychain + @brief The mediator object to access to the system Keychain services. + */ +@property (readonly, nonatomic, strong) FIRAuthKeychain *keychain; + +/** @property userDefaults + @brief The mediator object to access to the system User Defaults services. + */ +@property (readonly, nonatomic, strong) FIRAuthUserDefaultsStorage *userDefaults; + +/** @fn init + @brief The default initializer is disabled. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithServiceName: + @brief The designated initializer. + @param serviceName The service name to initialize with. + */ +- (instancetype)initWithServiceName:(NSString *)serviceName NS_DESIGNATED_INITIALIZER; + +/** @fn getStoredUserAccessGroupWithError: + @brief Get the user access group stored locally. + @param outError Return value for any error which occurs. + */ +- (NSString *_Nullable)getStoredUserAccessGroupWithError:(NSError *_Nullable *_Nullable)outError; + +/** @fn setStoredUserAccessGroup:error: + @brief The setter of the user access group stored locally. + @param accessGroup The access group to be set. + @param outError Return value for any error which occurs. + */ +- (BOOL)setStoredUserAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn getStoredUserForAccessGroup:projectID:error: + @brief The getter of the user stored locally. + @param accessGroup The access group to retrieve the user from. + @param projectIdentifier An identifier of the project that the user associates with. Currently, + we use API KEY. + @param outError Return value for any error which occurs. + */ +- (FIRUser *)getStoredUserForAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn setStoredUser:forAccessGroup:projectID:error: + @brief The setter of the user stored locally. + @param user The user to be stored. + @param accessGroup The access group to store the user in. + @param projectIdentifier An identifier of the project that the user associates with. Currently, + we use API KEY. + @param outError Return value for any error which occurs. + */ +- (BOOL)setStoredUser:(FIRUser *)user + forAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError; + +/** @fn removeStoredUserForAccessGroup:projectID:error: + @brief Remove the user that stored locally. + @param accessGroup The access group to remove the user from. + @param projectIdentifier An identifier of the project that the user associates with. Currently, + we use API KEY. + @param outError Return value for any error which occurs. + */ +- (BOOL)removeStoredUserForAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.m b/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.m new file mode 100644 index 00000000000..6a66e70b3c1 --- /dev/null +++ b/Firebase/Auth/Source/SystemService/FIRAuthStoredUserManager.m @@ -0,0 +1,125 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthStoredUserManager.h" + +/** @var kUserAccessGroupKey + @brief Key of user access group stored in user defaults. Used for retrieve the user access + group at launch. + */ +static NSString *kStoredUserAccessGroupKey = @"firebase_auth_stored_user_access_group"; + +/** @var kSharedKeychainAccountValue + @brief Default value for kSecAttrAccount of shared keychain items. + */ +static NSString *kSharedKeychainAccountValue = @"firebase_auth_firebase_user"; + +/** @var kStoredUserCoderKey + @brief The key to encode and decode the stored user. + */ +static NSString *kStoredUserCoderKey = @"firebase_auth_stored_user_coder_key"; + +@implementation FIRAuthStoredUserManager + +#pragma mark - Initializers + +- (instancetype)initWithServiceName:(NSString *)serviceName { + self = [super init]; + if (self) { + _keychain = [[FIRAuthKeychain alloc] initWithService:serviceName]; + _userDefaults = [[FIRAuthUserDefaultsStorage alloc] initWithService:serviceName]; + } + return self; +} + +#pragma mark - User Access Group + +- (NSString *_Nullable)getStoredUserAccessGroupWithError:(NSError *_Nullable *_Nullable)outError { + NSData *data = [self.userDefaults dataForKey:kStoredUserAccessGroupKey error:outError]; + if (data) { + NSString *userAccessGroup = [NSString stringWithUTF8String:data.bytes]; + return userAccessGroup; + } else { + return nil; + } +} + +- (BOOL)setStoredUserAccessGroup:(NSString *_Nullable)accessGroup + error:(NSError *_Nullable *_Nullable)outError { + NSData *data = [accessGroup dataUsingEncoding:NSUTF8StringEncoding]; + if (!data) { + return [self.userDefaults removeDataForKey:kStoredUserAccessGroupKey error:outError]; + } else { + return [self.userDefaults setData:data forKey:kStoredUserAccessGroupKey error:outError]; + } +} + +#pragma mark - User for Access Group + +- (FIRUser *)getStoredUserForAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError { + + + NSMutableDictionary *query = [[NSMutableDictionary alloc] init]; + query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; + + query[(__bridge id)kSecAttrAccessGroup] = accessGroup; + query[(__bridge id)kSecAttrService] = projectIdentifier; + query[(__bridge id)kSecAttrAccount] = kSharedKeychainAccountValue; + + NSData *data = [self.keychain getItemWithQuery:query error:outError]; + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; + FIRUser *user = [unarchiver decodeObjectOfClass:[FIRUser class] forKey:kStoredUserCoderKey]; + + return user; +} + +- (BOOL)setStoredUser:(FIRUser *)user + forAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError { + NSMutableDictionary *query = [[NSMutableDictionary alloc] init]; + query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; + query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + + query[(__bridge id)kSecAttrAccessGroup] = accessGroup; + query[(__bridge id)kSecAttrService] = projectIdentifier; + query[(__bridge id)kSecAttrAccount] = kSharedKeychainAccountValue; + + NSMutableData *data = [NSMutableData data]; + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; + [archiver encodeObject:user forKey:kStoredUserCoderKey]; + [archiver finishEncoding]; + + return [self.keychain setItem:data withQuery:query error:outError]; +} + +- (BOOL)removeStoredUserForAccessGroup:(NSString *)accessGroup + projectIdentifier:(NSString *)projectIdentifier + error:(NSError *_Nullable *_Nullable)outError { + NSMutableDictionary *query = [[NSMutableDictionary alloc] init]; + query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; + query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + + query[(__bridge id)kSecAttrAccessGroup] = accessGroup; + query[(__bridge id)kSecAttrService] = projectIdentifier; + query[(__bridge id)kSecAttrAccount] = kSharedKeychainAccountValue; + + return [self.keychain removeItemWithQuery:query error:outError]; +} + +@end diff --git a/Firebase/Auth/Source/FIRSecureTokenService.h b/Firebase/Auth/Source/SystemService/FIRSecureTokenService.h similarity index 100% rename from Firebase/Auth/Source/FIRSecureTokenService.h rename to Firebase/Auth/Source/SystemService/FIRSecureTokenService.h diff --git a/Firebase/Auth/Source/FIRSecureTokenService.m b/Firebase/Auth/Source/SystemService/FIRSecureTokenService.m similarity index 100% rename from Firebase/Auth/Source/FIRSecureTokenService.m rename to Firebase/Auth/Source/SystemService/FIRSecureTokenService.m diff --git a/Firebase/Auth/Source/FIRAdditionalUserInfo.m b/Firebase/Auth/Source/User/FIRAdditionalUserInfo.m similarity index 100% rename from Firebase/Auth/Source/FIRAdditionalUserInfo.m rename to Firebase/Auth/Source/User/FIRAdditionalUserInfo.m diff --git a/Firebase/Auth/Source/FIRAdditionalUserInfo_Internal.h b/Firebase/Auth/Source/User/FIRAdditionalUserInfo_Internal.h similarity index 100% rename from Firebase/Auth/Source/FIRAdditionalUserInfo_Internal.h rename to Firebase/Auth/Source/User/FIRAdditionalUserInfo_Internal.h diff --git a/Firebase/Auth/Source/FIRUser.m b/Firebase/Auth/Source/User/FIRUser.m similarity index 97% rename from Firebase/Auth/Source/FIRUser.m rename to Firebase/Auth/Source/User/FIRUser.m index cbe360751d5..3529ff2f58e 100644 --- a/Firebase/Auth/Source/FIRUser.m +++ b/Firebase/Auth/Source/User/FIRUser.m @@ -39,6 +39,7 @@ #import "FIRGetAccountInfoResponse.h" #import "FIRGetOOBConfirmationCodeRequest.h" #import "FIRGetOOBConfirmationCodeResponse.h" +#import "FIROAuthCredential_Internal.h" #import "FIRSecureTokenService.h" #import "FIRSetAccountInfoRequest.h" #import "FIRSetAccountInfoResponse.h" @@ -57,7 +58,7 @@ #if TARGET_OS_IOS #import "FIRPhoneAuthProvider.h" -#import "AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h" +#import "FIRPhoneAuthCredential_Internal.h" #endif NS_ASSUME_NONNULL_BEGIN @@ -750,18 +751,16 @@ - (void)reloadWithCompletion:(nullable FIRUserProfileChangeCallback)completion { #pragma mark - -- (void)reauthenticateWithCredential:(FIRAuthCredential *)credential - completion:(nullable FIRUserProfileChangeCallback)completion { - FIRAuthDataResultCallback callback = ^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { - completion(error); - }; - [self reauthenticateAndRetrieveDataWithCredential:credential completion:callback]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (void)reauthenticateWithCredential:(FIRAuthCredential *) credential + completion:(nullable FIRAuthDataResultCallback) completion { + [self reauthenticateAndRetrieveDataWithCredential:credential completion:completion]; } +#pragma clang diagnostic pop -- (void) - reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *) credential - completion:(nullable FIRAuthDataResultCallback) completion { +- (void)reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *) credential + completion:(nullable FIRAuthDataResultCallback) completion { dispatch_async(FIRAuthGlobalWorkQueue(), ^{ [self->_auth internalSignInAndRetrieveDataWithCredential:credential isReauthentication:YES @@ -972,14 +971,13 @@ - (void)internalGetTokenForcingRefresh:(BOOL)forceRefresh }]; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)linkWithCredential:(FIRAuthCredential *)credential - completion:(nullable FIRAuthResultCallback)completion { - FIRAuthDataResultCallback callback = ^(FIRAuthDataResult *_Nullable authResult, - NSError *_Nullable error) { - completion(authResult.user, error); - }; - [self linkAndRetrieveDataWithCredential:credential completion:callback]; + completion:(nullable FIRAuthDataResultCallback)completion { + [self linkAndRetrieveDataWithCredential:credential completion:completion]; } +#pragma clang diagnostic pop - (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential completion:(nullable FIRAuthDataResultCallback)completion { @@ -1110,8 +1108,12 @@ - (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential } FIRAdditionalUserInfo *additionalUserInfo = [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response]; + FIROAuthCredential *updatedOAuthCredential = + [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:response]; FIRAuthDataResult *result = - [[FIRAuthDataResult alloc] initWithUser:self additionalUserInfo:additionalUserInfo]; + [[FIRAuthDataResult alloc] initWithUser:self + additionalUserInfo:additionalUserInfo + credential:updatedOAuthCredential]; // Update the new token and refresh user info again. self->_tokenService = [[FIRSecureTokenService alloc] initWithRequestConfiguration:requestConfiguration @@ -1191,7 +1193,7 @@ - (void)unlinkFromProvider:(NSString *)provider return; } - // We can't just use the provider info objects in FIRSetAcccountInfoResponse because they + // We can't just use the provider info objects in FIRSetAccountInfoResponse because they // don't have localID and email fields. Remove the specific provider manually. NSMutableDictionary *mutableProviderData = [self->_providerData mutableCopy]; [mutableProviderData removeObjectForKey:provider]; diff --git a/Firebase/Auth/Source/FIRUserInfoImpl.h b/Firebase/Auth/Source/User/FIRUserInfoImpl.h similarity index 100% rename from Firebase/Auth/Source/FIRUserInfoImpl.h rename to Firebase/Auth/Source/User/FIRUserInfoImpl.h diff --git a/Firebase/Auth/Source/FIRUserInfoImpl.m b/Firebase/Auth/Source/User/FIRUserInfoImpl.m similarity index 100% rename from Firebase/Auth/Source/FIRUserInfoImpl.m rename to Firebase/Auth/Source/User/FIRUserInfoImpl.m diff --git a/Firebase/Auth/Source/FIRUserMetadata.m b/Firebase/Auth/Source/User/FIRUserMetadata.m similarity index 100% rename from Firebase/Auth/Source/FIRUserMetadata.m rename to Firebase/Auth/Source/User/FIRUserMetadata.m diff --git a/Firebase/Auth/Source/FIRUserMetadata_Internal.h b/Firebase/Auth/Source/User/FIRUserMetadata_Internal.h similarity index 100% rename from Firebase/Auth/Source/FIRUserMetadata_Internal.h rename to Firebase/Auth/Source/User/FIRUserMetadata_Internal.h diff --git a/Firebase/Auth/Source/FIRUser_Internal.h b/Firebase/Auth/Source/User/FIRUser_Internal.h similarity index 100% rename from Firebase/Auth/Source/FIRUser_Internal.h rename to Firebase/Auth/Source/User/FIRUser_Internal.h diff --git a/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.h b/Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthDefaultUIDelegate.h rename to Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.h diff --git a/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m b/Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m rename to Firebase/Auth/Source/Utilities/FIRAuthDefaultUIDelegate.m diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.h b/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.h similarity index 97% rename from Firebase/Auth/Source/FIRAuthErrorUtils.h rename to Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.h index c09fda27897..410956bd61a 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.h +++ b/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.h @@ -219,10 +219,12 @@ NS_ASSUME_NONNULL_BEGIN /** @fn accountExistsWithDifferentCredentialErrorWithEmail: @brief Constructs an @c NSError with the @c FIRAuthErrorAccountExistsWithDifferentCredential code. - @param Email The email address that is already associated with an existing account + @param email The email address that is already associated with an existing account + @param updatedCredential The updated credential for the existing account @return The NSError instance associated with the given FIRAuthError. */ -+ (NSError *)accountExistsWithDifferentCredentialErrorWithEmail:(nullable NSString *)Email; ++ (NSError *)accountExistsWithDifferentCredentialErrorWithEmail:(nullable NSString *)email + updatedCredential:(nullable FIRAuthCredential *)updatedCredential; /** @fn providerAlreadyLinkedErrorWithMessage: @brief Constructs an @c NSError with the @c FIRAuthErrorCodeProviderAlreadyLinked code. @@ -522,6 +524,13 @@ NS_ASSUME_NONNULL_BEGIN */ + (NSError *)nullUserErrorWithMessage:(nullable NSString *)message; +/** @fn invalidProviderIDErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeInvalidProviderID code. + @param message Error message from the backend, if any. + @remarks This error indicates that the provider id given for the web operation is invalid. + */ ++ (NSError *)invalidProviderIDErrorWithMessage:(nullable NSString *)message; + /** @fn invalidDynamicLinkDomainErrorWithMessage: @brief Constructs an @c NSError with the code and message provided. @param message Error message from the backend, if any. diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.m b/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.m similarity index 98% rename from Firebase/Auth/Source/FIRAuthErrorUtils.m rename to Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.m index ef6a5141840..b6988b799d7 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.m +++ b/Firebase/Auth/Source/Utilities/FIRAuthErrorUtils.m @@ -37,10 +37,6 @@ NSString *const FIRAuthErrorUserInfoNameKey = @"FIRAuthErrorUserInfoNameKey"; -NSString *const FIRAuthErrorNameKey = @"error_name"; - -NSString *const FIRAuthUpdatedCredentialKey = @"FIRAuthUpdatedCredentialKey"; - /** @var kServerErrorDetailMarker @brief This marker indicates that the server error message contains a detail error message which should be used instead of the hardcoded client error message. @@ -424,6 +420,12 @@ static NSString *const kFIRAuthErrorMessageNullUser = @"A null user object was provided as the " "argument for an operation which requires a non-null user object."; +/** @var kFIRAuthErrorMessageInvalidProviderID + @brief Message for @c FIRAuthErrorCodeInvalidProviderID error code. + */ +static NSString *const kFIRAuthErrorMessageInvalidProviderID = @"The provider ID provided for the " + "attempted web operation is invalid."; + /** @var kFIRAuthErrorMessageInvalidDynamicLinkDomain @brief Message for @c kFIRAuthErrorMessageInvalidDynamicLinkDomain error code. */ @@ -559,6 +561,8 @@ return kFIRAuthErrorMessageWebRequestFailed; case FIRAuthErrorCodeNullUser: return kFIRAuthErrorMessageNullUser; + case FIRAuthErrorCodeInvalidProviderID: + return kFIRAuthErrorMessageInvalidProviderID; case FIRAuthErrorCodeInvalidDynamicLinkDomain: return kFIRAuthErrorMessageInvalidDynamicLinkDomain; case FIRAuthErrorCodeWebInternalError: @@ -690,6 +694,8 @@ return @"ERROR_WEB_NETWORK_REQUEST_FAILED"; case FIRAuthErrorCodeNullUser: return @"ERROR_NULL_USER"; + case FIRAuthErrorCodeInvalidProviderID: + return @"ERROR_INVALID_PROVIDER_ID"; case FIRAuthErrorCodeInvalidDynamicLinkDomain: return @"ERROR_INVALID_DYNAMIC_LINK_DOMAIN"; case FIRAuthErrorCodeWebInternalError: @@ -743,11 +749,6 @@ + (NSError *)errorWithCode:(FIRAuthInternalErrorCode)code if (!errorUserInfo[NSLocalizedDescriptionKey]) { errorUserInfo[NSLocalizedDescriptionKey] = FIRAuthErrorDescription(errorCode); } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // TODO(wangyue): Remove the deprecated code on next breaking change. - errorUserInfo[FIRAuthErrorNameKey] = FIRAuthErrorCodeString(errorCode); -#pragma clang diagnostic pop errorUserInfo[FIRAuthErrorUserInfoNameKey] = FIRAuthErrorCodeString(errorCode); return [NSError errorWithDomain:FIRAuthErrorDomain code:errorCode userInfo:errorUserInfo]; } else { @@ -905,12 +906,14 @@ + (NSError *)invalidEmailErrorWithMessage:(nullable NSString *)message { return [self errorWithCode:FIRAuthInternalErrorCodeInvalidEmail message:message]; } -+ (NSError *)accountExistsWithDifferentCredentialErrorWithEmail:(nullable NSString *)email { - NSDictionary *userInfo; - if (email.length) { - userInfo = @{ - FIRAuthErrorUserInfoEmailKey : email, - }; ++ (NSError *)accountExistsWithDifferentCredentialErrorWithEmail:(nullable NSString *)email + updatedCredential:(nullable FIRAuthCredential *)updatedCredential { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (email) { + userInfo[FIRAuthErrorUserInfoEmailKey] = email; + } + if (updatedCredential) { + userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey] = updatedCredential; } return [self errorWithCode:FIRAuthInternalErrorCodeAccountExistsWithDifferentCredential userInfo:userInfo]; @@ -945,11 +948,6 @@ + (NSError *)credentialAlreadyInUseErrorWithMessage:(nullable NSString *)message email:(nullable NSString *)email { NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; if (credential) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // TODO(wangyue): Remove the deprecated code on next breaking change. - userInfo[FIRAuthUpdatedCredentialKey] = credential; -#pragma clang diagnostic pop userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey] = credential; } if (email.length) { @@ -1136,6 +1134,10 @@ + (NSError *)nullUserErrorWithMessage:(nullable NSString *)message { return [self errorWithCode:FIRAuthInternalErrorCodeNullUser message:message]; } ++ (NSError *)invalidProviderIDErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeInvalidProviderID message:message]; +} + + (NSError *)invalidDynamicLinkDomainErrorWithMessage:(nullable NSString *)message { return [self errorWithCode:FIRAuthInternalErrorCodeInvalidDynamicLinkDomain message:message]; } diff --git a/Firebase/Auth/Source/FIRAuthExceptionUtils.h b/Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthExceptionUtils.h rename to Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.h diff --git a/Firebase/Auth/Source/FIRAuthExceptionUtils.m b/Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthExceptionUtils.m rename to Firebase/Auth/Source/Utilities/FIRAuthExceptionUtils.m diff --git a/Firebase/Auth/Source/FIRAuthInternalErrors.h b/Firebase/Auth/Source/Utilities/FIRAuthInternalErrors.h similarity index 98% rename from Firebase/Auth/Source/FIRAuthInternalErrors.h rename to Firebase/Auth/Source/Utilities/FIRAuthInternalErrors.h index 0db204ca4b9..205dc0f3ab0 100644 --- a/Firebase/Auth/Source/FIRAuthInternalErrors.h +++ b/Firebase/Auth/Source/Utilities/FIRAuthInternalErrors.h @@ -355,7 +355,7 @@ typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) { FIRAuthInternalErrorCodeWebInternalError = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebInternalError, - /** Indicates that an internal error occured within a SFSafariViewController or UIWebview. + /** Indicates that an internal error occurred within a SFSafariViewController or UIWebview. */ FIRAuthInternalErrorCodeWebSignInUserInteractionFailure = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebSignInUserInteractionFailure, @@ -393,6 +393,11 @@ typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) { FIRAuthInternalErrorCodeNullUser = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeNullUser, + /** Indicates that the provider id given for the web operation is invalid. + */ + FIRAuthInternalErrorCodeInvalidProviderID = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeInvalidProviderID, + /** Indicates that the Firebase Dynamic Link domain used is either not configured or is unauthorized for the current project. */ diff --git a/Firebase/Auth/Source/FIRAuthURLPresenter.h b/Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthURLPresenter.h rename to Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.h diff --git a/Firebase/Auth/Source/FIRAuthURLPresenter.m b/Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthURLPresenter.m rename to Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.m diff --git a/Firebase/Auth/Source/FIRAuthWebUtils.h b/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthWebUtils.h rename to Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h diff --git a/Firebase/Auth/Source/FIRAuthWebUtils.m b/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthWebUtils.m rename to Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m diff --git a/Firebase/Auth/Source/FIRAuthWebView.h b/Firebase/Auth/Source/Utilities/FIRAuthWebView.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthWebView.h rename to Firebase/Auth/Source/Utilities/FIRAuthWebView.h diff --git a/Firebase/Auth/Source/FIRAuthWebView.m b/Firebase/Auth/Source/Utilities/FIRAuthWebView.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthWebView.m rename to Firebase/Auth/Source/Utilities/FIRAuthWebView.m diff --git a/Firebase/Auth/Source/FIRAuthWebViewController.h b/Firebase/Auth/Source/Utilities/FIRAuthWebViewController.h similarity index 100% rename from Firebase/Auth/Source/FIRAuthWebViewController.h rename to Firebase/Auth/Source/Utilities/FIRAuthWebViewController.h diff --git a/Firebase/Auth/Source/FIRAuthWebViewController.m b/Firebase/Auth/Source/Utilities/FIRAuthWebViewController.m similarity index 100% rename from Firebase/Auth/Source/FIRAuthWebViewController.m rename to Firebase/Auth/Source/Utilities/FIRAuthWebViewController.m diff --git a/Firebase/Auth/Source/NSData+FIRBase64.h b/Firebase/Auth/Source/Utilities/NSData+FIRBase64.h similarity index 100% rename from Firebase/Auth/Source/NSData+FIRBase64.h rename to Firebase/Auth/Source/Utilities/NSData+FIRBase64.h diff --git a/Firebase/Auth/Source/NSData+FIRBase64.m b/Firebase/Auth/Source/Utilities/NSData+FIRBase64.m similarity index 100% rename from Firebase/Auth/Source/NSData+FIRBase64.m rename to Firebase/Auth/Source/Utilities/NSData+FIRBase64.m diff --git a/Firebase/Core/CHANGELOG.md b/Firebase/Core/CHANGELOG.md index 775c7de2905..bdfee8569f2 100644 --- a/Firebase/Core/CHANGELOG.md +++ b/Firebase/Core/CHANGELOG.md @@ -1,4 +1,18 @@ -# Unreleased +# v6.0.0 -- M47 +- [changed] Added support for CocoaPods 1.7.x `:generate_multiple_pod_projects` feature. (#2751) +- [removed] Remove FIRAnalyticsConfiguration from Public header. Use from FirebaseAnalytics. (#2728) +- [changed] Remove runtime warning for missing analytics in favor of one at build time. (#2734) + +# 2019-04-02 -- v5.4.1 -- M46 +- [changed] Avoid using NSRegularExpression in FIRApp. +- [changed] Improve error meessage for invalid app names. (#2614) +- [changed] FIRApp thread safety fixes. (#2639) + +# 2019-03-19 -- v5.4.0 -- M45 +- [changed] Allow Bundle IDs that have a valid prefix to enable richer extension support. (#2515) +- [changed] Deprecated `FIRAnalyticsConfiguration` API in favor of new methods on the Analytics SDK. + Please call the new APIs directly: Enable/disable Analytics with `Analytics.setAnalyticsCollectionEnabled(_)` + and modify the session timeout interval with `Analytics.setSessionTimeoutInterval(_)`. # 2019-01-22 -- v5.2.0 -- M41 - [changed] Added a registerInternalLibrary API. Now other Firebase libraries register with FirebaseCore diff --git a/Firebase/Core/FIRAnalyticsConfiguration.m b/Firebase/Core/FIRAnalyticsConfiguration.m index 33aa1687f52..a57936b9bc7 100644 --- a/Firebase/Core/FIRAnalyticsConfiguration.m +++ b/Firebase/Core/FIRAnalyticsConfiguration.m @@ -12,11 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "FIRAnalyticsConfiguration.h" +#import -#import "Private/FIRAnalyticsConfiguration+Internal.h" +#import "Private/FIRAnalyticsConfiguration.h" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" @implementation FIRAnalyticsConfiguration +#pragma clang diagnostic pop + (FIRAnalyticsConfiguration *)sharedInstance { static FIRAnalyticsConfiguration *sharedInstance = nil; @@ -36,16 +39,6 @@ - (void)postNotificationName:(NSString *)name value:(id)value { userInfo:@{name : value}]; } -- (void)setMinimumSessionInterval:(NSTimeInterval)minimumSessionInterval { - [self postNotificationName:kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification - value:@(minimumSessionInterval)]; -} - -- (void)setSessionTimeoutInterval:(NSTimeInterval)sessionTimeoutInterval { - [self postNotificationName:kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification - value:@(sessionTimeoutInterval)]; -} - - (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled { [self setAnalyticsCollectionEnabled:analyticsCollectionEnabled persistSetting:YES]; } diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m index 799db26f5c8..d3b6c460ece 100644 --- a/Firebase/Core/FIRApp.m +++ b/Firebase/Core/FIRApp.m @@ -15,11 +15,11 @@ #include #import "FIRApp.h" -#import "FIRConfiguration.h" -#import "Private/FIRAnalyticsConfiguration+Internal.h" +#import "Private/FIRAnalyticsConfiguration.h" #import "Private/FIRAppInternal.h" #import "Private/FIRBundleUtil.h" #import "Private/FIRComponentContainerInternal.h" +#import "Private/FIRConfigurationInternal.h" #import "Private/FIRLibrary.h" #import "Private/FIRLogger.h" #import "Private/FIROptionsInternal.h" @@ -161,14 +161,16 @@ + (void)configureWithName:(NSString *)name options:(FIROptions *)options { if (!((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || character == '_' || character == '-')) { [NSException raise:kFirebaseCoreErrorDomain - format:@"App name should only contain Letters, " - @"Numbers, Underscores, and Dashes."]; + format:@"App name can only contain alphanumeric (A-Z,a-z,0-9), " + @"hyphen (-), and underscore (_) characters"]; } } - if (sAllApps && sAllApps[name]) { - [NSException raise:kFirebaseCoreErrorDomain - format:@"App named %@ has already been configured.", name]; + @synchronized(self) { + if (sAllApps && sAllApps[name]) { + [NSException raise:kFirebaseCoreErrorDomain + format:@"App named %@ has already been configured.", name]; + } } FIRLogDebug(kFIRLoggerCore, @"I-COR000002", @"Configuring app named %@", name); @@ -214,18 +216,19 @@ + (NSDictionary *)allApps { if (!sAllApps) { FIRLogError(kFIRLoggerCore, @"I-COR000005", @"No app has been configured yet."); } - NSDictionary *dict = [NSDictionary dictionaryWithDictionary:sAllApps]; - return dict; + return [sAllApps copy]; } } // Public only for tests + (void)resetApps { - sDefaultApp = nil; - [sAllApps removeAllObjects]; - sAllApps = nil; - [sLibraryVersions removeAllObjects]; - sLibraryVersions = nil; + @synchronized(self) { + sDefaultApp = nil; + [sAllApps removeAllObjects]; + sAllApps = nil; + [sLibraryVersions removeAllObjects]; + sLibraryVersions = nil; + } } - (void)deleteApp:(FIRAppVoidBoolCallback)completion { @@ -308,11 +311,7 @@ - (BOOL)configureCore { // always initialize first by itself before the other SDKs. if ([self.name isEqualToString:kFIRDefaultAppName]) { Class firAnalyticsClass = NSClassFromString(@"FIRAnalytics"); - if (!firAnalyticsClass) { - FIRLogWarning(kFIRLoggerCore, @"I-COR000022", - @"Firebase Analytics is not available. To add it, include Firebase/Core in the " - @"Podfile or add FirebaseAnalytics.framework to the Link Build Phase"); - } else { + if (firAnalyticsClass) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" SEL startWithConfigurationSelector = @selector(startWithConfiguration:options:); @@ -320,6 +319,7 @@ - (BOOL)configureCore { if ([firAnalyticsClass respondsToSelector:startWithConfigurationSelector]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [firAnalyticsClass performSelector:startWithConfigurationSelector withObject:[FIRConfiguration sharedInstance].analyticsConfiguration withObject:_options]; @@ -360,9 +360,12 @@ - (void)setDataCollectionDefaultEnabled:(BOOL)dataCollectionDefaultEnabled { } // The Analytics flag has not been explicitly set, so update with the value being set. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [[FIRAnalyticsConfiguration sharedInstance] setAnalyticsCollectionEnabled:dataCollectionDefaultEnabled persistSetting:NO]; +#pragma clang diagnostic pop } - (BOOL)isDataCollectionDefaultEnabled { @@ -419,8 +422,10 @@ + (void)sendNotificationsToSDKs:(FIRApp *)app { // This is the new way of sending information to SDKs. // TODO: Do we want this on a background thread, maybe? - for (Class library in sRegisteredAsConfigurable) { - [library configureWithApp:app]; + @synchronized(self) { + for (Class library in sRegisteredAsConfigurable) { + [library configureWithApp:app]; + } } } @@ -472,10 +477,12 @@ + (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString * // add the name/version pair to the dictionary. if ([name rangeOfCharacterFromSet:disallowedSet].location == NSNotFound && [version rangeOfCharacterFromSet:disallowedSet].location == NSNotFound) { - if (!sLibraryVersions) { - sLibraryVersions = [[NSMutableDictionary alloc] init]; + @synchronized(self) { + if (!sLibraryVersions) { + sLibraryVersions = [[NSMutableDictionary alloc] init]; + } + sLibraryVersions[name] = version; } - sLibraryVersions[name] = version; } else { FIRLogError(kFIRLoggerCore, @"I-COR000027", @"The library name (%@) or version number (%@) contain invalid characters. " @@ -504,20 +511,24 @@ + (void)registerInternalLibrary:(nonnull Class)library dispatch_once(&onceToken, ^{ sRegisteredAsConfigurable = [[NSMutableArray alloc] init]; }); - [sRegisteredAsConfigurable addObject:library]; + @synchronized(self) { + [sRegisteredAsConfigurable addObject:library]; + } } [self registerLibrary:name withVersion:version]; } + (NSString *)firebaseUserAgent { - NSMutableArray *libraries = - [[NSMutableArray alloc] initWithCapacity:sLibraryVersions.count]; - for (NSString *libraryName in sLibraryVersions) { - [libraries - addObject:[NSString stringWithFormat:@"%@/%@", libraryName, sLibraryVersions[libraryName]]]; + @synchronized(self) { + NSMutableArray *libraries = + [[NSMutableArray alloc] initWithCapacity:sLibraryVersions.count]; + for (NSString *libraryName in sLibraryVersions) { + [libraries addObject:[NSString stringWithFormat:@"%@/%@", libraryName, + sLibraryVersions[libraryName]]]; + } + [libraries sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + return [libraries componentsJoinedByString:@" "]; } - [libraries sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; - return [libraries componentsJoinedByString:@" "]; } - (void)checkExpectedBundleID { @@ -525,8 +536,8 @@ - (void)checkExpectedBundleID { NSString *expectedBundleID = [self expectedBundleID]; // The checking is only done when the bundle ID is provided in the serviceInfo dictionary for // backward compatibility. - if (expectedBundleID != nil && ![FIRBundleUtil hasBundleIdentifier:expectedBundleID - inBundles:bundles]) { + if (expectedBundleID != nil && ![FIRBundleUtil hasBundleIdentifierPrefix:expectedBundleID + inBundles:bundles]) { FIRLogError(kFIRLoggerCore, @"I-COR000008", @"The project's Bundle ID is inconsistent with " @"either the Bundle ID in '%@.%@', or the Bundle ID in the options if you are " @@ -571,33 +582,32 @@ + (BOOL)validateAppID:(NSString *)appID { return NO; } - // All app IDs must start with at least ":". - NSString *const versionPattern = @"^\\d+:"; - NSRegularExpression *versionRegex = - [NSRegularExpression regularExpressionWithPattern:versionPattern options:0 error:NULL]; - if (!versionRegex) { + NSScanner *stringScanner = [NSScanner scannerWithString:appID]; + stringScanner.charactersToBeSkipped = nil; + + NSString *appIDVersion; + if (![stringScanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] + intoString:&appIDVersion]) { return NO; } - NSRange appIDRange = NSMakeRange(0, appID.length); - NSArray *versionMatches = [versionRegex matchesInString:appID options:0 range:appIDRange]; - if (versionMatches.count != 1) { + if (![stringScanner scanString:@":" intoString:NULL]) { + // appIDVersion must be separated by ":" return NO; } - NSRange versionRange = [(NSTextCheckingResult *)versionMatches.firstObject range]; - NSString *appIDVersion = [appID substringWithRange:versionRange]; - NSArray *knownVersions = @[ @"1:" ]; + NSArray *knownVersions = @[ @"1" ]; if (![knownVersions containsObject:appIDVersion]) { // Permit unknown yet properly formatted app ID versions. + FIRLogInfo(kFIRLoggerCore, @"I-COR000010", @"Unknown GOOGLE_APP_ID version: %@", appIDVersion); return YES; } - if (![FIRApp validateAppIDFormat:appID withVersion:appIDVersion]) { + if (![self validateAppIDFormat:appID withVersion:appIDVersion]) { return NO; } - if (![FIRApp validateAppIDFingerprint:appID withVersion:appIDVersion]) { + if (![self validateAppIDFingerprint:appID withVersion:appIDVersion]) { return NO; } @@ -627,33 +637,76 @@ + (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version { return NO; } - if (![version hasSuffix:@":"]) { + NSScanner *stringScanner = [NSScanner scannerWithString:appID]; + stringScanner.charactersToBeSkipped = nil; + + // Skip version part + // '**::ios:' + if (![stringScanner scanString:version intoString:NULL]) { + // The version part is missing or mismatched + return NO; + } + + // Validate version part (see part between '*' symbols below) + // '*:*:ios:' + if (![stringScanner scanString:@":" intoString:NULL]) { + // appIDVersion must be separated by ":" + return NO; + } + + // Validate version part (see part between '*' symbols below) + // ':**:ios:'. + NSInteger projectNumber = NSNotFound; + if (![stringScanner scanInteger:&projectNumber]) { + // NO project number found. + return NO; + } + + // Validate version part (see part between '*' symbols below) + // ':*:*ios:'. + if (![stringScanner scanString:@":" intoString:NULL]) { + // The project number must be separated by ":" return NO; } - if (![appID hasPrefix:version]) { + // Validate version part (see part between '*' symbols below) + // '::*ios*:'. + NSString *platform; + if (![stringScanner scanUpToString:@":" intoString:&platform]) { return NO; } - NSString *const pattern = @"^\\d+:ios:[a-f0-9]+$"; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern - options:0 - error:NULL]; - if (!regex) { + if (![platform isEqualToString:@"ios"]) { + // The platform must be @"ios" return NO; } - NSRange localRange = NSMakeRange(version.length, appID.length - version.length); - NSUInteger numberOfMatches = [regex numberOfMatchesInString:appID options:0 range:localRange]; - if (numberOfMatches != 1) { + // Validate version part (see part between '*' symbols below) + // '::ios*:*'. + if (![stringScanner scanString:@":" intoString:NULL]) { + // The platform must be separated by ":" return NO; } + + // Validate version part (see part between '*' symbols below) + // '::ios:**'. + unsigned long long fingerprint = NSNotFound; + if (![stringScanner scanHexLongLong:&fingerprint]) { + // Fingerprint part is missing + return NO; + } + + if (!stringScanner.isAtEnd) { + // There are not allowed characters in the fingerprint part + return NO; + } + return YES; } /** * Validates that the fingerprint of the app ID string is what is expected based on the supplied - * version. The version must end in ":". + * version. * * Note that the v1 hash algorithm is not permitted on the client and cannot be fully validated. * @@ -663,18 +716,6 @@ + (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version { * otherwise. */ + (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version { - if (!appID.length || !version.length) { - return NO; - } - - if (![version hasSuffix:@":"]) { - return NO; - } - - if (![appID hasPrefix:version]) { - return NO; - } - // Extract the supplied fingerprint from the supplied app ID. // This assumes the app ID format is the same for all known versions below. If the app ID format // changes in future versions, the tokenizing of the app ID format will need to take into account @@ -695,7 +736,7 @@ + (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)versi return NO; } - if ([version isEqual:@"1:"]) { + if ([version isEqual:@"1"]) { // The v1 hash algorithm is not permitted on the client so the actual hash cannot be validated. return YES; } diff --git a/Firebase/Core/FIRBundleUtil.m b/Firebase/Core/FIRBundleUtil.m index 93ee02e97db..841833a8519 100644 --- a/Firebase/Core/FIRBundleUtil.m +++ b/Firebase/Core/FIRBundleUtil.m @@ -14,6 +14,8 @@ #import "Private/FIRBundleUtil.h" +#import + @implementation FIRBundleUtil + (NSArray *)relevantBundles { @@ -45,13 +47,29 @@ + (NSArray *)relevantURLSchemes { return result; } -+ (BOOL)hasBundleIdentifier:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles { ++ (BOOL)hasBundleIdentifierPrefix:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles { for (NSBundle *bundle in bundles) { - if ([bundle.bundleIdentifier isEqualToString:bundleIdentifier]) { + // This allows app extensions that have the app's bundle as their prefix to pass this test. + NSString *applicationBundleIdentifier = + [GULAppEnvironmentUtil isAppExtension] + ? [self bundleIdentifierByRemovingLastPartFrom:bundleIdentifier] + : bundleIdentifier; + + if ([applicationBundleIdentifier isEqualToString:bundle.bundleIdentifier]) { return YES; } } return NO; } ++ (NSString *)bundleIdentifierByRemovingLastPartFrom:(NSString *)bundleIdentifier { + NSString *bundleIDComponentsSeparator = @"."; + + NSMutableArray *bundleIDComponents = + [[bundleIdentifier componentsSeparatedByString:bundleIDComponentsSeparator] mutableCopy]; + [bundleIDComponents removeLastObject]; + + return [bundleIDComponents componentsJoinedByString:bundleIDComponentsSeparator]; +} + @end diff --git a/Firebase/Core/FIRConfiguration.m b/Firebase/Core/FIRConfiguration.m index cd6486257ef..869f73d7f65 100644 --- a/Firebase/Core/FIRConfiguration.m +++ b/Firebase/Core/FIRConfiguration.m @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "FIRConfiguration.h" +#import "Private/FIRConfigurationInternal.h" + +#import "Private/FIRAnalyticsConfiguration.h" extern void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); diff --git a/Firebase/Core/FIRErrors.m b/Firebase/Core/FIRErrors.m index 6d6d52d2ce8..72120c5c98f 100644 --- a/Firebase/Core/FIRErrors.m +++ b/Firebase/Core/FIRErrors.m @@ -15,15 +15,7 @@ #import "Private/FIRErrors.h" NSString *const kFirebaseErrorDomain = @"com.firebase"; -NSString *const kFirebaseAdMobErrorDomain = @"com.firebase.admob"; -NSString *const kFirebaseAppInviteErrorDomain = @"com.firebase.appinvite"; -NSString *const kFirebaseAuthErrorDomain = @"com.firebase.auth"; -NSString *const kFirebaseCloudMessagingErrorDomain = @"com.firebase.cloudmessaging"; NSString *const kFirebaseConfigErrorDomain = @"com.firebase.config"; NSString *const kFirebaseCoreErrorDomain = @"com.firebase.core"; -NSString *const kFirebaseCrashReportingErrorDomain = @"com.firebase.crashreporting"; -NSString *const kFirebaseDatabaseErrorDomain = @"com.firebase.database"; -NSString *const kFirebaseDurableDeepLinkErrorDomain = @"com.firebase.durabledeeplink"; -NSString *const kFirebaseInstanceIDErrorDomain = @"com.firebase.instanceid"; NSString *const kFirebasePerfErrorDomain = @"com.firebase.perf"; NSString *const kFirebaseStorageErrorDomain = @"com.firebase.storage"; diff --git a/Firebase/Core/FIRLogger.m b/Firebase/Core/FIRLogger.m index b8c40e9747d..532a96c294a 100644 --- a/Firebase/Core/FIRLogger.m +++ b/Firebase/Core/FIRLogger.m @@ -20,23 +20,17 @@ #import "Private/FIRVersion.h" +FIRLoggerService kFIRLoggerCore = @"[Firebase/Core]"; + +// All the FIRLoggerService definitions should be migrated to clients. Do not add new ones! FIRLoggerService kFIRLoggerABTesting = @"[Firebase/ABTesting]"; FIRLoggerService kFIRLoggerAdMob = @"[Firebase/AdMob]"; FIRLoggerService kFIRLoggerAnalytics = @"[Firebase/Analytics]"; FIRLoggerService kFIRLoggerAuth = @"[Firebase/Auth]"; -FIRLoggerService kFIRLoggerCore = @"[Firebase/Core]"; FIRLoggerService kFIRLoggerCrash = @"[Firebase/Crash]"; -FIRLoggerService kFIRLoggerDatabase = @"[Firebase/Database]"; -FIRLoggerService kFIRLoggerDynamicLinks = @"[Firebase/DynamicLinks]"; -FIRLoggerService kFIRLoggerFirestore = @"[Firebase/Firestore]"; -FIRLoggerService kFIRLoggerInstanceID = @"[Firebase/InstanceID]"; -FIRLoggerService kFIRLoggerInvites = @"[Firebase/Invites]"; FIRLoggerService kFIRLoggerMLKit = @"[Firebase/MLKit]"; -FIRLoggerService kFIRLoggerMessaging = @"[Firebase/Messaging]"; FIRLoggerService kFIRLoggerPerf = @"[Firebase/Performance]"; FIRLoggerService kFIRLoggerRemoteConfig = @"[Firebase/RemoteConfig]"; -FIRLoggerService kFIRLoggerStorage = @"[Firebase/Storage]"; -FIRLoggerService kFIRLoggerSwizzler = @"[FirebaseSwizzlingUtilities]"; /// Arguments passed on launch. NSString *const kFIRDisableDebugModeApplicationArgument = @"-FIRDebugDisabled"; @@ -106,6 +100,20 @@ void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel) { GULSetLoggerLevel((GULLoggerLevel)loggerLevel); } +#ifdef DEBUG +void FIRResetLogger() { + extern void GULResetLogger(void); + sFIRLoggerOnceToken = 0; + [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey]; + sFIRLoggerUserDefaults = nil; + GULResetLogger(); +} + +void FIRSetLoggerUserDefaults(NSUserDefaults *defaults) { + sFIRLoggerUserDefaults = defaults; +} +#endif + /** * Check if the level is high enough to be loggable. * diff --git a/Firebase/Core/FIROptions.m b/Firebase/Core/FIROptions.m index 0650420738d..3e6a3a0458f 100644 --- a/Firebase/Core/FIROptions.m +++ b/Firebase/Core/FIROptions.m @@ -17,6 +17,7 @@ #import "Private/FIRErrors.h" #import "Private/FIRLogger.h" #import "Private/FIROptionsInternal.h" +#import "Private/FIRVersion.h" // Keys for the strings in the plist file. NSString *const kFIRAPIKey = @"API_KEY"; @@ -39,11 +40,13 @@ NSString *const kFIRIsAnalyticsEnabled = @"IS_ANALYTICS_ENABLED"; NSString *const kFIRIsSignInEnabled = @"IS_SIGNIN_ENABLED"; -// Library version ID. -NSString *const kFIRLibraryVersionID = @"5" // Major version (one or more digits) - @"03" // Minor version (exactly 2 digits) - @"01" // Build number (exactly 2 digits) - @"000"; // Fixed "000" +// Library version ID formatted like: +// @"5" // Major version (one or more digits) +// @"04" // Minor version (exactly 2 digits) +// @"01" // Build number (exactly 2 digits) +// @"000"; // Fixed "000" +NSString *kFIRLibraryVersionID; + // Plist file name. NSString *const kServiceInfoFileName = @"GoogleService-Info"; // Plist file type. @@ -109,17 +112,9 @@ + (FIROptions *)defaultOptions { + (void)initialize { // Report FirebaseCore version for useragent string - NSRange major = NSMakeRange(0, 1); - NSRange minor = NSMakeRange(1, 2); - NSRange patch = NSMakeRange(3, 2); - [FIRApp - registerLibrary:@"fire-ios" - withVersion:[NSString stringWithFormat:@"%@.%d.%d", - [kFIRLibraryVersionID substringWithRange:major], - [[kFIRLibraryVersionID substringWithRange:minor] - intValue], - [[kFIRLibraryVersionID substringWithRange:patch] - intValue]]]; + [FIRApp registerLibrary:@"fire-ios" + withVersion:[NSString stringWithUTF8String:FIRCoreVersionString]]; + NSDictionary *info = [[NSBundle mainBundle] infoDictionary]; NSString *xcodeVersion = info[@"DTXcodeBuild"]; NSString *sdkVersion = info[@"DTSDKBuild"]; @@ -295,6 +290,16 @@ - (void)setGoogleAppID:(NSString *)googleAppID { } - (NSString *)libraryVersionID { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // The unit tests are set up to catch anything that does not properly convert. + NSString *version = [NSString stringWithUTF8String:FIRCoreVersionString]; + NSArray *components = [version componentsSeparatedByString:@"."]; + NSString *major = [components objectAtIndex:0]; + NSString *minor = [NSString stringWithFormat:@"%02d", [[components objectAtIndex:1] intValue]]; + NSString *patch = [NSString stringWithFormat:@"%02d", [[components objectAtIndex:2] intValue]]; + kFIRLibraryVersionID = [NSString stringWithFormat:@"%@%@%@000", major, minor, patch]; + }); return kFIRLibraryVersionID; } diff --git a/Firebase/Core/Private/FIRAnalyticsConfiguration+Internal.h b/Firebase/Core/Private/FIRAnalyticsConfiguration.h similarity index 83% rename from Firebase/Core/Private/FIRAnalyticsConfiguration+Internal.h rename to Firebase/Core/Private/FIRAnalyticsConfiguration.h index be624b49410..6429ac70eac 100644 --- a/Firebase/Core/Private/FIRAnalyticsConfiguration+Internal.h +++ b/Firebase/Core/Private/FIRAnalyticsConfiguration.h @@ -14,7 +14,7 @@ * limitations under the License. */ -#import "FIRAnalyticsConfiguration.h" +#import /// Values stored in analyticsEnabledState. Never alter these constants since they must match with /// values persisted to disk. @@ -38,7 +38,14 @@ static NSString *const kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotifi static NSString *const kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification = @"FIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification"; -@interface FIRAnalyticsConfiguration (Internal) +@interface FIRAnalyticsConfiguration : NSObject + +/// Returns the shared instance of FIRAnalyticsConfiguration. ++ (FIRAnalyticsConfiguration *)sharedInstance; + +// Sets whether analytics collection is enabled for this app on this device. This setting is +// persisted across app sessions. By default it is enabled. +- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled; /// Sets whether analytics collection is enabled for this app on this device, and a flag to persist /// the value or not. The setting should not be persisted if being set by the global data collection diff --git a/Firebase/Core/Private/FIRAppInternal.h b/Firebase/Core/Private/FIRAppInternal.h index d663d3ec8b1..09728cd33be 100644 --- a/Firebase/Core/Private/FIRAppInternal.h +++ b/Firebase/Core/Private/FIRAppInternal.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#import "FIRApp.h" -#import "FIRErrors.h" +#import +#import @class FIRComponentContainer; @protocol FIRLibrary; @@ -40,11 +40,9 @@ typedef NS_ENUM(NSInteger, FIRConfigType) { extern NSString *const kFIRServiceAdMob; extern NSString *const kFIRServiceAuth; extern NSString *const kFIRServiceAuthUI; -extern NSString *const kFIRServiceCrash; extern NSString *const kFIRServiceDatabase; extern NSString *const kFIRServiceDynamicLinks; extern NSString *const kFIRServiceInstanceID; -extern NSString *const kFIRServiceInvites; extern NSString *const kFIRServiceMessaging; extern NSString *const kFIRServiceMeasurement; extern NSString *const kFIRServiceRemoteConfig; diff --git a/Firebase/Core/Private/FIRBundleUtil.h b/Firebase/Core/Private/FIRBundleUtil.h index c458a2c4c6d..d9475dd29e0 100644 --- a/Firebase/Core/Private/FIRBundleUtil.h +++ b/Firebase/Core/Private/FIRBundleUtil.h @@ -45,8 +45,9 @@ + (NSArray *)relevantURLSchemes; /** - * Checks if the bundle identifier exists in the given bundles. + * Checks if any of the given bundles have a matching bundle identifier prefix (removing extension + * suffixes). */ -+ (BOOL)hasBundleIdentifier:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles; ++ (BOOL)hasBundleIdentifierPrefix:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles; @end diff --git a/Firebase/Core/Private/FIRConfigurationInternal.h b/Firebase/Core/Private/FIRConfigurationInternal.h new file mode 100644 index 00000000000..ee168867079 --- /dev/null +++ b/Firebase/Core/Private/FIRConfigurationInternal.h @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRConfiguration.h" + +@class FIRAnalyticsConfiguration; + +@interface FIRConfiguration () + +/** + * The configuration class for Firebase Analytics. This should be removed once the logic for + * enabling and disabling Analytics is moved to Analytics. + */ +@property(nonatomic, readwrite) FIRAnalyticsConfiguration *analyticsConfiguration; + +@end diff --git a/Firebase/Core/Private/FIRErrorCode.h b/Firebase/Core/Private/FIRErrorCode.h index 01d3c56e36b..f77b3d00246 100644 --- a/Firebase/Core/Private/FIRErrorCode.h +++ b/Firebase/Core/Private/FIRErrorCode.h @@ -34,22 +34,5 @@ typedef NS_ENUM(NSInteger, FIRErrorCode) { /** * Error code for failing to configure a specific service. */ - FIRErrorCodeAdMobFailed = -110, - FIRErrorCodeAppInviteFailed = -112, - FIRErrorCodeCloudMessagingFailed = -113, FIRErrorCodeConfigFailed = -114, - FIRErrorCodeDatabaseFailed = -115, - FIRErrorCodeCrashReportingFailed = -118, - FIRErrorCodeDurableDeepLinkFailed = -119, - FIRErrorCodeAuthFailed = -120, - FIRErrorCodeInstanceIDFailed = -121, - FIRErrorCodeStorageFailed = -123, - - /** - * Error codes returned by Dynamic Links - */ - FIRErrorCodeDynamicLinksStrongMatchNotAvailable = -124, - FIRErrorCodeDynamicLinksManualRetrievalNotEnabled = -125, - FIRErrorCodeDynamicLinksPendingLinkOnlyAvailableAtFirstLaunch = -126, - FIRErrorCodeDynamicLinksPendingLinkRetrievalAlreadyRunning = -127, }; diff --git a/Firebase/Core/Private/FIRErrors.h b/Firebase/Core/Private/FIRErrors.h index cf69252aa97..19e47328ace 100644 --- a/Firebase/Core/Private/FIRErrors.h +++ b/Firebase/Core/Private/FIRErrors.h @@ -19,15 +19,6 @@ #include "FIRErrorCode.h" extern NSString *const kFirebaseErrorDomain; -extern NSString *const kFirebaseAdMobErrorDomain; -extern NSString *const kFirebaseAppInviteErrorDomain; -extern NSString *const kFirebaseAuthErrorDomain; -extern NSString *const kFirebaseCloudMessagingErrorDomain; extern NSString *const kFirebaseConfigErrorDomain; extern NSString *const kFirebaseCoreErrorDomain; -extern NSString *const kFirebaseCrashReportingErrorDomain; -extern NSString *const kFirebaseDatabaseErrorDomain; -extern NSString *const kFirebaseDurableDeepLinkErrorDomain; -extern NSString *const kFirebaseInstanceIDErrorDomain; extern NSString *const kFirebasePerfErrorDomain; -extern NSString *const kFirebaseStorageErrorDomain; diff --git a/Firebase/Core/Private/FIRLogger.h b/Firebase/Core/Private/FIRLogger.h index a538199be17..548e389a4ef 100644 --- a/Firebase/Core/Private/FIRLogger.h +++ b/Firebase/Core/Private/FIRLogger.h @@ -16,7 +16,7 @@ #import -#import "FIRLoggerLevel.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -29,19 +29,11 @@ extern FIRLoggerService kFIRLoggerABTesting; extern FIRLoggerService kFIRLoggerAdMob; extern FIRLoggerService kFIRLoggerAnalytics; extern FIRLoggerService kFIRLoggerAuth; -extern FIRLoggerService kFIRLoggerCore; extern FIRLoggerService kFIRLoggerCrash; -extern FIRLoggerService kFIRLoggerDatabase; -extern FIRLoggerService kFIRLoggerDynamicLinks; -extern FIRLoggerService kFIRLoggerFirestore; -extern FIRLoggerService kFIRLoggerInstanceID; -extern FIRLoggerService kFIRLoggerInvites; +extern FIRLoggerService kFIRLoggerCore; extern FIRLoggerService kFIRLoggerMLKit; -extern FIRLoggerService kFIRLoggerMessaging; extern FIRLoggerService kFIRLoggerPerf; extern FIRLoggerService kFIRLoggerRemoteConfig; -extern FIRLoggerService kFIRLoggerStorage; -extern FIRLoggerService kFIRLoggerSwizzler; /** * The key used to store the logger's error count. diff --git a/Firebase/Core/Private/FIROptionsInternal.h b/Firebase/Core/Private/FIROptionsInternal.h index 7bb40fc10d6..117efdaa0fa 100644 --- a/Firebase/Core/Private/FIROptionsInternal.h +++ b/Firebase/Core/Private/FIROptionsInternal.h @@ -14,7 +14,7 @@ * limitations under the License. */ -#import "FIROptions.h" +#import /** * Keys for the strings in the plist file. diff --git a/Firebase/Core/Public/FIRAnalyticsConfiguration.h b/Firebase/Core/Public/FIRAnalyticsConfiguration.h deleted file mode 100644 index 7f62c1450a3..00000000000 --- a/Firebase/Core/Public/FIRAnalyticsConfiguration.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * This class provides configuration fields for Firebase Analytics. - */ -NS_SWIFT_NAME(AnalyticsConfiguration) -@interface FIRAnalyticsConfiguration : NSObject - -/** - * Returns the shared instance of FIRAnalyticsConfiguration. - */ -+ (FIRAnalyticsConfiguration *)sharedInstance NS_SWIFT_NAME(shared()); - -/** - * Deprecated. - * Sets the minimum engagement time in seconds required to start a new session. The default value - * is 10 seconds. - */ -- (void)setMinimumSessionInterval:(NSTimeInterval)minimumSessionInterval - DEPRECATED_MSG_ATTRIBUTE( - "Sessions are started immediately. More information at https://bit.ly/2FU46av"); - -/** - * Sets the interval of inactivity in seconds that terminates the current session. The default - * value is 1800 seconds (30 minutes). - */ -- (void)setSessionTimeoutInterval:(NSTimeInterval)sessionTimeoutInterval; - -/** - * Sets whether analytics collection is enabled for this app on this device. This setting is - * persisted across app sessions. By default it is enabled. - */ -- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firebase/Core/Public/FIRConfiguration.h b/Firebase/Core/Public/FIRConfiguration.h index 95bba5e7b39..8de3b076a62 100644 --- a/Firebase/Core/Public/FIRConfiguration.h +++ b/Firebase/Core/Public/FIRConfiguration.h @@ -16,14 +16,12 @@ #import -#import "FIRAnalyticsConfiguration.h" -#import "FIRLoggerLevel.h" +#import NS_ASSUME_NONNULL_BEGIN /** - * This interface provides global level properties that the developer can tweak, and the singleton - * of the Firebase Analytics configuration class. + * This interface provides global level properties that the developer can tweak. */ NS_SWIFT_NAME(FirebaseConfiguration) @interface FIRConfiguration : NSObject @@ -31,9 +29,6 @@ NS_SWIFT_NAME(FirebaseConfiguration) /** Returns the shared configuration object. */ @property(class, nonatomic, readonly) FIRConfiguration *sharedInstance NS_SWIFT_NAME(shared); -/** The configuration class for Firebase Analytics. */ -@property(nonatomic, readwrite) FIRAnalyticsConfiguration *analyticsConfiguration; - /** * Sets the logging level for internal Firebase logging. Firebase will only log messages * that are logged at or below loggerLevel. The messages are logged both to the Xcode diff --git a/Firebase/Core/Public/FirebaseCore.h b/Firebase/Core/Public/FirebaseCore.h index fa26f694b75..95119aed924 100644 --- a/Firebase/Core/Public/FirebaseCore.h +++ b/Firebase/Core/Public/FirebaseCore.h @@ -14,7 +14,6 @@ * limitations under the License. */ -#import "FIRAnalyticsConfiguration.h" #import "FIRApp.h" #import "FIRConfiguration.h" #import "FIRLoggerLevel.h" diff --git a/Firebase/Database/CHANGELOG.md b/Firebase/Database/CHANGELOG.md index b7eb71cfc74..092f4f2ea0e 100644 --- a/Firebase/Database/CHANGELOG.md +++ b/Firebase/Database/CHANGELOG.md @@ -1,3 +1,9 @@ +# v6.0.0 +- [removed] Remove deprecated `childByAppendingPath` API. (#2763) + +# v5.1.1 +- [fixed] Fixed crash in FSRWebSocket. (#2485) + # v5.0.2 - [fixed] Fixed undefined behavior sanitizer issues. (#1443, #1444) diff --git a/Firebase/Database/FIRDatabaseReference.m b/Firebase/Database/FIRDatabaseReference.m index 3ea5992f08f..9d49124e6f2 100644 --- a/Firebase/Database/FIRDatabaseReference.m +++ b/Firebase/Database/FIRDatabaseReference.m @@ -90,10 +90,6 @@ - (FIRDatabaseReference *) root { #pragma mark - #pragma mark Child methods -- (FIRDatabaseReference *)childByAppendingPath:(NSString *)pathString { - return [self child:pathString]; -} - - (FIRDatabaseReference *)child:(NSString *)pathString { if ([self.path getFront] == nil) { // we're at the root diff --git a/Firebase/Database/Public/FIRDatabaseReference.h b/Firebase/Database/Public/FIRDatabaseReference.h index b96585e65a4..e6a4891acdc 100644 --- a/Firebase/Database/Public/FIRDatabaseReference.h +++ b/Firebase/Database/Public/FIRDatabaseReference.h @@ -51,11 +51,6 @@ NS_SWIFT_NAME(DatabaseReference) */ - (FIRDatabaseReference *)child:(NSString *)pathString; -/** - * childByAppendingPath: is deprecated, use child: instead. - */ -- (FIRDatabaseReference *)childByAppendingPath:(NSString *)pathString __deprecated_msg("use child: instead"); - /** * childByAutoId generates a new child location using a unique key and returns a * FIRDatabaseReference to it. This is useful when the children of a Firebase Database diff --git a/Firebase/Database/Utilities/FUtilities.h b/Firebase/Database/Utilities/FUtilities.h index f7fe7a54455..2f2c154fa2f 100644 --- a/Firebase/Database/Utilities/FUtilities.h +++ b/Firebase/Database/Utilities/FUtilities.h @@ -15,6 +15,8 @@ */ #import +#import + #import "FParsedUrl.h" @interface FUtilities : NSObject @@ -70,6 +72,7 @@ FOUNDATION_EXPORT NSString *const kFPersistenceLogTag; } \ } while(0) +extern FIRLoggerService kFIRLoggerDatabase; BOOL FFIsLoggingEnabled(FLogLevel logLevel); void firebaseUncaughtExceptionHandler(NSException *exception); void firebaseJobsTroll(void); diff --git a/Firebase/Database/Utilities/FUtilities.m b/Firebase/Database/Utilities/FUtilities.m index d0a9a437fb8..21704ca9eef 100644 --- a/Firebase/Database/Utilities/FUtilities.m +++ b/Firebase/Database/Utilities/FUtilities.m @@ -27,6 +27,7 @@ #pragma mark - #pragma mark C functions +FIRLoggerService kFIRLoggerDatabase = @"[Firebase/Database]"; static FLogLevel logLevel = FLogLevelInfo; // Default log level is info static NSMutableDictionary* options = nil; diff --git a/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m b/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m index 4cd481b4e61..d0c1e20dd5d 100644 --- a/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m +++ b/Firebase/Database/third_party/SocketRocket/FSRWebSocket.m @@ -1354,7 +1354,11 @@ - (void)_sendFrameWithOpcode:(FSROpCode)opcode data:(id)data; { [self assertOnWorkQueue]; - NSAssert(data == nil || [data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"Function expects nil, NSString or NSData"); + if (data == nil) { + return; + } + + NSAssert([data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"Function expects nil, NSString or NSData"); size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; diff --git a/Firebase/Database/third_party/SocketRocket/LICENSE b/Firebase/Database/third_party/SocketRocket/LICENSE new file mode 100644 index 00000000000..1582810d62a --- /dev/null +++ b/Firebase/Database/third_party/SocketRocket/LICENSE @@ -0,0 +1,53 @@ +Copyright 2012 Square Inc. + +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. + +$OpenBSD: base64.c,v 1.5 2006/10/21 09:55:03 otto Exp $ + +Copyright (c) 1996 by Internet Software Consortium. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS +ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE +CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +Portions Copyright (c) 1995 by International Business Machines, Inc. + +International Business Machines, Inc. (hereinafter called IBM) grants +permission under its copyrights to use, copy, modify, and distribute this +Software with or without fee, provided that the above copyright notice and +all paragraphs of this notice appear in all copies, and that the name of IBM +not be used in connection with the marketing of any product incorporating +the Software or modifications thereof, without specific, written prior +permission. + +To the extent it has a right to do so, IBM grants an immunity from suit +under its patents, if any, for the use, sale or manufacture of products to +the extent that such products are used for performing Domain Name System +dynamic updates in TCP/IP networks by means of the Software. No immunity is +granted for any product per se or for any other function of any product. + +THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, +DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING +OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN +IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/Firebase/Database/third_party/Wrap-leveldb/LICENSE b/Firebase/Database/third_party/Wrap-leveldb/LICENSE new file mode 100644 index 00000000000..eda4ff89e23 --- /dev/null +++ b/Firebase/Database/third_party/Wrap-leveldb/LICENSE @@ -0,0 +1,46 @@ +Created by Adam Preble on 1/23/12. +Copyright (c) 2012 Adam Preble. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +Portions of APLevelDB are based on LevelDB-ObjC: + https:github.com/hoisie/LevelDB-ObjC +Specifically the SliceFromString/StringFromSlice macros, and the structure of +the enumeration methods. License for those potions follows: + +Copyright (c) 2011 Pave Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Firebase/DynamicLinks/CHANGELOG.md b/Firebase/DynamicLinks/CHANGELOG.md index 5cd5dc4af2a..07258408918 100644 --- a/Firebase/DynamicLinks/CHANGELOG.md +++ b/Firebase/DynamicLinks/CHANGELOG.md @@ -1,3 +1,13 @@ +# v4.0 +- FirebaseAnalytics is no longer a hard dependency in the DynamicLinks pod. If you were installing Dynamic Links via pod ''Firebase/DynamicLinks'', you should add 'pod 'Firebase/Analytics'' to the Podfile to maintain full Dynamic Links functionality. If you previously have 'pod 'Firebase/Core'' in the Podfile, no change is necessary. (#2738) +- Remove deprecated API in FDLURLComponents. (#2768) + +# v3.4.3 +- Fixed an issue where matchesshortlinkformat was returning true for certain FDL long links. + +# v3.4.2 +- Fixes an issue with certain analytics attribution parameters not being recorded on an app install. (#2462) + # v3.4.1 - Return call validation for sysctlbyname. (#2394) diff --git a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents+Private.h b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents+Private.h index 2e4745cfdf8..68207503ecc 100644 --- a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents+Private.h +++ b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents+Private.h @@ -16,6 +16,11 @@ #import "DynamicLinks/Public/FDLURLComponents.h" +/** + * Label exceptions from FDL. + */ +FOUNDATION_EXPORT NSString *_Nonnull const kFirebaseDurableDeepLinkErrorDomain; + NS_ASSUME_NONNULL_BEGIN /// Each of the parameter classes used in FIRDynamicLinkURLComponents needs to be able to diff --git a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m index 5b7cf853777..7c6a12e6ca1 100644 --- a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m +++ b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m @@ -23,6 +23,9 @@ #import "DynamicLinks/Logging/FDLLogging.h" #import "DynamicLinks/Utilities/FDLUtilities.h" +// Label exceptions from FDL. +NSString *const kFirebaseDurableDeepLinkErrorDomain = @"com.firebase.durabledeeplink"; + /// The exact behavior of dict[key] = value is unclear when value is nil. This function safely adds /// the key-value pair to the dictionary, even when value is nil. /// This function will treat empty string in the same way as nil. @@ -45,7 +48,6 @@ @implementation FIRDynamicLinkGoogleAnalyticsParameters { static NSString *const kFDLUTMCampaignKey = @"utm_campaign"; static NSString *const kFDLUTMTermKey = @"utm_term"; static NSString *const kFDLUTMContentKey = @"utm_content"; -static NSString *const kFirebaseDurableDeepLinkErrorDomain = @"com.firebase.durabledeeplink"; + (instancetype)parameters { return [[self alloc] init]; diff --git a/Firebase/DynamicLinks/FIRDynamicLink+Private.h b/Firebase/DynamicLinks/FIRDynamicLink+Private.h index 72bb666bb1a..43b310e8975 100644 --- a/Firebase/DynamicLinks/FIRDynamicLink+Private.h +++ b/Firebase/DynamicLinks/FIRDynamicLink+Private.h @@ -41,11 +41,11 @@ typedef NS_ENUM(NSUInteger, FIRDynamicLinkMatchConfidence) { @property(nonatomic, copy, nullable) NSString *matchMessage; -@property(nonatomic, copy, readonly) NSDictionary *parametersDictionary; +@property(nonatomic, copy, readonly) NSDictionary *parametersDictionary; @property(nonatomic, assign, readwrite) FIRDLMatchType matchType; -- (instancetype)initWithParametersDictionary:(NSDictionary *)parametersDictionary; +- (instancetype)initWithParametersDictionary:(NSDictionary *)parametersDictionary; @end diff --git a/Firebase/DynamicLinks/FIRDynamicLink.m b/Firebase/DynamicLinks/FIRDynamicLink.m index cbe2e23d973..bbe7c4ef5a7 100644 --- a/Firebase/DynamicLinks/FIRDynamicLink.m +++ b/Firebase/DynamicLinks/FIRDynamicLink.m @@ -28,10 +28,12 @@ - (NSString *)description { self.minimumAppVersion ?: @"N/A", self.matchMessage]; } -- (instancetype)initWithParametersDictionary:(NSDictionary *)parameters { +- (instancetype)initWithParametersDictionary:(NSDictionary *)parameters { NSParameterAssert(parameters.count > 0); if (self = [super init]) { + _parametersDictionary = [parameters copy]; + NSString *urlString = parameters[kFIRDLParameterDeepLinkIdentifier]; _url = [NSURL URLWithString:urlString]; _inviteId = parameters[kFIRDLParameterInviteId]; @@ -39,26 +41,62 @@ - (instancetype)initWithParametersDictionary:(NSDictionary *)parameters { _minimumAppVersion = parameters[kFIRDLParameterMinimumAppVersion]; if (parameters[kFIRDLParameterMatchType]) { - _matchType = [[self class] matchTypeWithString:parameters[kFIRDLParameterMatchType]]; + [self setMatchType:[[self class] matchTypeWithString:parameters[kFIRDLParameterMatchType]]]; } else if (_url || _inviteId) { // If matchType not present assume unique match for compatibility with server side behavior // on iOS 8. - _matchType = FIRDLMatchTypeUnique; + [self setMatchType:FIRDLMatchTypeUnique]; } + _matchMessage = parameters[kFIRDLParameterMatchMessage]; } return self; } -- (NSDictionary *)parametersDictionary { - NSMutableDictionary *parametersDictionary = [NSMutableDictionary dictionary]; - parametersDictionary[kFIRDLParameterInviteId] = _inviteId; - parametersDictionary[kFIRDLParameterDeepLinkIdentifier] = [_url absoluteString]; - parametersDictionary[kFIRDLParameterMatchType] = [[self class] stringWithMatchType:_matchType]; - parametersDictionary[kFIRDLParameterWeakMatchEndpoint] = _weakMatchEndpoint; - parametersDictionary[kFIRDLParameterMinimumAppVersion] = _minimumAppVersion; - parametersDictionary[kFIRDLParameterMatchMessage] = _matchMessage; - return parametersDictionary; +#pragma mark - Properties + +- (void)setUrl:(NSURL *)url { + _url = [url copy]; + [self setParametersDictionaryValue:[_url absoluteString] + forKey:kFIRDLParameterDeepLinkIdentifier]; +} + +- (void)setMinimumAppVersion:(NSString *)minimumAppVersion { + _minimumAppVersion = [minimumAppVersion copy]; + [self setParametersDictionaryValue:_minimumAppVersion forKey:kFIRDLParameterMinimumAppVersion]; +} + +- (void)setInviteId:(NSString *)inviteId { + _inviteId = [inviteId copy]; + [self setParametersDictionaryValue:_inviteId forKey:kFIRDLParameterInviteId]; +} + +- (void)setWeakMatchEndpoint:(NSString *)weakMatchEndpoint { + _weakMatchEndpoint = [weakMatchEndpoint copy]; + [self setParametersDictionaryValue:_weakMatchEndpoint forKey:kFIRDLParameterWeakMatchEndpoint]; +} + +- (void)setMatchType:(FIRDLMatchType)matchType { + _matchType = matchType; + [self setParametersDictionaryValue:[[self class] stringWithMatchType:_matchType] + forKey:kFIRDLParameterMatchType]; +} + +- (void)setMatchMessage:(NSString *)matchMessage { + _matchMessage = [matchMessage copy]; + [self setParametersDictionaryValue:_matchMessage forKey:kFIRDLParameterMatchMessage]; +} + +- (void)setParametersDictionaryValue:(id)value forKey:(NSString *)key { + NSMutableDictionary *parametersDictionary = + [self.parametersDictionary mutableCopy]; + if (value == nil) { + [parametersDictionary removeObjectForKey:key]; + } else { + parametersDictionary[key] = value; + } + + _parametersDictionary = [parametersDictionary copy]; } - (FIRDynamicLinkMatchConfidence)matchConfidence { diff --git a/Firebase/DynamicLinks/FIRDynamicLinkNetworking.h b/Firebase/DynamicLinks/FIRDynamicLinkNetworking.h index d92d2bca5f8..e6977b9a6aa 100644 --- a/Firebase/DynamicLinks/FIRDynamicLinkNetworking.h +++ b/Firebase/DynamicLinks/FIRDynamicLinkNetworking.h @@ -64,7 +64,7 @@ typedef NS_ENUM(NSInteger, FIRDynamicLinkNetworkingRetrievalProcessType) { */ void FIRMakeHTTPRequest(NSURLRequest *request, FIRNetworkRequestCompletionHandler completion); -/** The base of the FDL API URL, Used in AppInvites to switch prod/staging backend */ +/** The base of the FDL API URL */ FOUNDATION_EXPORT NSString *const kApiaryRestBaseUrl; /** diff --git a/Firebase/DynamicLinks/FIRDynamicLinks.m b/Firebase/DynamicLinks/FIRDynamicLinks.m index 5b940015abf..d670a925b78 100644 --- a/Firebase/DynamicLinks/FIRDynamicLinks.m +++ b/Firebase/DynamicLinks/FIRDynamicLinks.m @@ -29,6 +29,9 @@ #import "DynamicLinks/FIRDLScionLogging.h" #endif +#ifdef FIRDynamicLinks3P +#import "DynamicLinks/FDLURLComponents/FDLURLComponents+Private.h" +#endif #import "DynamicLinks/FIRDLRetrievalProcessFactory.h" #import "DynamicLinks/FIRDLRetrievalProcessProtocols.h" #import "DynamicLinks/FIRDLRetrievalProcessResult.h" @@ -82,6 +85,9 @@ @interface FIRDynamicLinks () @end #ifdef FIRDynamicLinks3P +// Error code from FDL. +static const NSInteger FIRErrorCodeDurableDeepLinkFailed = -119; + @interface FIRDynamicLinks () { /// Stored Analytics reference, if it exists. id _Nullable _analytics; diff --git a/Firebase/DynamicLinks/Logging/FDLLogging.m b/Firebase/DynamicLinks/Logging/FDLLogging.m index 1e6934fa43f..ca968d6cfc6 100644 --- a/Firebase/DynamicLinks/Logging/FDLLogging.m +++ b/Firebase/DynamicLinks/Logging/FDLLogging.m @@ -18,6 +18,8 @@ #ifdef GIN_SCION_LOGGING #import + +FIRLoggerService kFIRLoggerDynamicLinks = @"[Firebase/DynamicLinks]"; #endif // GIN_SCION_LOGGING #ifdef GIN_SCION_LOGGING diff --git a/Firebase/DynamicLinks/Public/FDLURLComponents.h b/Firebase/DynamicLinks/Public/FDLURLComponents.h index 1cc8397560c..3177859c55e 100644 --- a/Firebase/DynamicLinks/Public/FDLURLComponents.h +++ b/Firebase/DynamicLinks/Public/FDLURLComponents.h @@ -505,38 +505,6 @@ FIR_SWIFT_NAME(DynamicLinkComponents) */ @property(nonatomic, nullable, readonly) NSURL *url; -/** - * @method componentsWithLink:domain: - * @abstract Generates a Dynamic Link URL components object with the minimum necessary parameters - * set to generate a fully-functional Dynamic Link. - * @param link Deep link to be stored in created Dynamic link. This link also called "payload" of - * the Dynamic link. - * @param domain Domain of your App. This value must be equal to your assigned domain from Firebase - * Console. (e.g. xyz.page.link). Note that the domain scheme is required to be https and is - * assumed as such by this API. - */ -+ (instancetype)componentsWithLink:(NSURL *)link - domain:(NSString *)domain - NS_SWIFT_UNAVAILABLE("Use init(link:domain:)")DEPRECATED_MSG_ATTRIBUTE( - "This method is deprecated. Please use the new method with support for " - "domainURIPrefix- init(link:domainURIPrefix:)."); - -/** - * @method initWithLink:domain: - * @abstract Generates a Dynamic Link URL components object with the minimum necessary parameters - * set to generate a fully-functional Dynamic Link. - * @param link Deep link to be stored in created Dynamic link. This link also called "payload" of - * the Dynamic link. - * @param domain Domain of your App. This value must be equal to your assigned domain from Firebase - * Console. (e.g. xyz.page.link). Note that the domain scheme is required to be https and is - * assumed as such by this API. - */ -- (instancetype)initWithLink:(NSURL *)link - domain:(NSString *)domain - DEPRECATED_MSG_ATTRIBUTE( - "This method is deprecated. Please use the new method with support for " - "domainURIPrefix- init(link:domainURIPrefix:)."); - /** * @method componentsWithLink:domainURIPrefix: * @abstract Generates a Dynamic Link URL components object with the minimum necessary parameters diff --git a/Firebase/DynamicLinks/Utilities/FDLUtilities.m b/Firebase/DynamicLinks/Utilities/FDLUtilities.m index 564507fb14a..4201de0a2e1 100644 --- a/Firebase/DynamicLinks/Utilities/FDLUtilities.m +++ b/Firebase/DynamicLinks/Utilities/FDLUtilities.m @@ -211,12 +211,11 @@ BOOL FIRDLIsURLForWhiteListedCustomDomain(NSURL *_Nullable URL) { options:NSCaseInsensitiveSearch | NSAnchoredSearch] .location) == 0) { // The (short) URL needs to be longer than the domainURIPrefix, it's first character after - // the domainURIPrefix needs to be '/' or '?' and should be followed by at-least one more + // the domainURIPrefix needs to be '/' and should be followed by at-least one more // character. if (urlStr.length > domainURIPrefixStr.length + 1 && - ([urlStr characterAtIndex:domainURIPrefixStr.length] == '/' || - [urlStr characterAtIndex:domainURIPrefixStr.length] == '?')) { - // Check if there are any more '/' after the first '/' or '?' trailing the + ([urlStr characterAtIndex:domainURIPrefixStr.length] == '/')) { + // Check if there are any more '/' after the first '/'trailing the // domainURIPrefix. This does not apply to unique match links copied from the clipboard. // The clipboard links will have '?link=' after the domainURIPrefix. NSString *urlWithoutDomainURIPrefix = @@ -249,17 +248,17 @@ BOOL FIRDLCanParseUniversalLinkURL(NSURL *_Nullable URL) { } BOOL FIRDLMatchesShortLinkFormat(NSURL *URL) { - // Short Durable Link URLs always have a path, except for certain custom domain URLs e.g. - // 'https://google.com?link=abcd' will not have a path component. - // FIRDLIsURLForWhiteListedCustomDomain implicitely checks for path component in custom domain - // URLs. - BOOL hasPath = URL.path.length > 0 || FIRDLIsURLForWhiteListedCustomDomain(URL); + // Short Durable Link URLs always have a path. + BOOL hasPath = URL.path.length > 0; + BOOL matchesRegularExpression = + ([URL.path rangeOfString:@"/[^/]+" options:NSRegularExpressionSearch].location != NSNotFound); // Must be able to parse (also checks if the URL conforms to *.app.goo.gl/* or goo.gl/app/*) - BOOL canParse = FIRDLCanParseUniversalLinkURL(URL); + BOOL canParse = FIRDLCanParseUniversalLinkURL(URL) | FIRDLIsURLForWhiteListedCustomDomain(URL); + ; // Path cannot be prefixed with /link/dismiss BOOL isDismiss = [[URL.path lowercaseString] hasPrefix:@"/link/dismiss"]; - return hasPath && !isDismiss && canParse; + return hasPath && matchesRegularExpression && !isDismiss && canParse; } NSString *FIRDLMatchTypeStringFromServerString(NSString *_Nullable serverMatchTypeString) { diff --git a/Firebase/InAppMessaging/Runtime/FIRInAppMessaging+Bootstrap.m b/Firebase/InAppMessaging/Runtime/FIRInAppMessaging+Bootstrap.m index 1f3134adb44..c4c5509e5b0 100644 --- a/Firebase/InAppMessaging/Runtime/FIRInAppMessaging+Bootstrap.m +++ b/Firebase/InAppMessaging/Runtime/FIRInAppMessaging+Bootstrap.m @@ -26,7 +26,6 @@ #import "FIRIAMClearcutUploader.h" #import "FIRIAMRuntimeManager.h" #import "FIRIAMSDKSettings.h" -#import "FIROptionsInternal.h" #import "NSString+FIRInterlaceStrings.h" @implementation FIRInAppMessaging (Bootstrap) diff --git a/Firebase/InAppMessagingDisplay/CHANGELOG.md b/Firebase/InAppMessagingDisplay/CHANGELOG.md index 7189f327252..ece3d26671f 100644 --- a/Firebase/InAppMessagingDisplay/CHANGELOG.md +++ b/Firebase/InAppMessagingDisplay/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2019-03-19 -- v0.13.1 +- Fixed a crash (#2498) that occurred when integrating In-App Messaging into NativeScript apps. + # 2019-03-05 -- v0.13.0 - Added a feature allowing developers to programmatically register a delegate for updates on in-app engagement (impression, click, display errors). diff --git a/Firebase/InAppMessagingDisplay/FIDRenderingWindowHelper.m b/Firebase/InAppMessagingDisplay/FIDRenderingWindowHelper.m index 6714dbd640e..c133efc60a3 100644 --- a/Firebase/InAppMessagingDisplay/FIDRenderingWindowHelper.m +++ b/Firebase/InAppMessagingDisplay/FIDRenderingWindowHelper.m @@ -24,8 +24,7 @@ + (UIWindow *)UIWindowForModalView { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window]; - UIWindowForModal = [[UIWindow alloc] initWithFrame:[appWindow frame]]; + UIWindowForModal = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIWindowForModal.windowLevel = UIWindowLevelNormal; }); return UIWindowForModal; @@ -36,8 +35,7 @@ + (UIWindow *)UIWindowForBannerView { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window]; - UIWindowForBanner = [[FIDBannerViewUIWindow alloc] initWithFrame:[appWindow frame]]; + UIWindowForBanner = [[FIDBannerViewUIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIWindowForBanner.windowLevel = UIWindowLevelNormal; }); @@ -49,8 +47,7 @@ + (UIWindow *)UIWindowForImageOnlyView { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window]; - UIWindowForImageOnly = [[UIWindow alloc] initWithFrame:[appWindow frame]]; + UIWindowForImageOnly = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIWindowForImageOnly.windowLevel = UIWindowLevelNormal; }); diff --git a/Firebase/InstanceID/CHANGELOG.md b/Firebase/InstanceID/CHANGELOG.md new file mode 100644 index 00000000000..ed100bab4ba --- /dev/null +++ b/Firebase/InstanceID/CHANGELOG.md @@ -0,0 +1,156 @@ +# 2019-05-07 -- 4.0.0 +- Remove deprecated `token` method. Use `instanceIDWithHandler:` instead. (#2741) +- Send `firebaseUserAgent` with a register request (#2679) + +# 2019-04-02 -- v3.8.1 +- Fixed handling the multiple calls of instanceIDWithHandler. (#2445) +- Fixed a race condition where token kept getting refreshed at app start. (#2438) + +# 2019-03-19 -- v3.8.0 +- Adding community support for tvOS. (#2428) +- Adding Firebase info to checkin. (#2509) +- Fixed a crash in FIRInstanceIDCheckinService. (#2548) + +# 2019-03-05 -- v3.7.0 +- Open source Firebase InstanceID. (#186) + +# 2019-02-20 -- v3.5.0 +- Always update keychain access control when adding new keychain to ensure it won't be blocked when device is locked. (#1399) + +# 2019-01-22 -- v3.4.0 +- Move all keychain write operations off the main thread. (#1399) +- Make keychain operations asynchronous where possible (given the current APIs) +- Avoid redundant keychain operations when it's been queried and cached before. + +# 2018-10-25 -- v3.3.0 +- Fixed a crash caused by keychain operation when accessing default access group. (#1399, #1393) +- Remove internal APIs that are no longer used. + +# 2018-09-25 -- v3.2.2 +- Fixed a crash caused by NSUserDefaults being called on background thread. + +# 2018-08-14 -- v3.2.1 +- Fixed an issue that checkin is not cached properly when app first started. (#1561) + +# 2018-07-31 -- v3.2.0 +- Added support for global Firebase data collection flag. (#1219) +- Improved message tracking sent by server API. +- Fixed an issue that InstanceID doesn't compile in app extensions, allowing its +dependents like remote config to be working inside the app extensions. + +# 2018-06-19 -- v3.1.1 +- Ensure the checkin and tokens are refreshed if firebase project changed. +- Fixed an issue that checkin should be turned off when FCM's autoInitEnabled flag is off. + +# 2018-06-12 -- v3.1.0 +- Added a new API to fetch InstanceID and Token with a completion handler. The completion handler returns a FIRInstanceIDResult with a instanceID and a token properties. +- Deprecated the token method. +- Added support to log a new customized label provided by developer. + +# 2018-05-08 -- v3.0.0 +- Removed deprecated method `setAPNSToken:type` defined in FIRInstanceID, please use `setAPNSToken:type` defined in FIRMessaging instead. +- Removed deprecated enum `FIRInstanceIDAPNSTokenType` defined in FIRInstanceID, please use `FIRMessagingAPNSTokenType` defined in FIRMessaging instead. +- Fixed an issue that FCM scheduled messages were not tracked successfully. + +# 2018-03-06 -- v2.0.10 +- Improved documentation on InstanceID usage for GDPR. +- Improved the keypair handling during GCM to FCM migration. If you are migrating from GCM to FCM, we encourage you to update to this version and above. + +# 2018-02-06 -- v2.0.9 +- Improved support for language targeting for FCM service. Server updates happen more efficiently when language changes. +- Improved support for FCM token auto generation enable/disable functions. + +# 2017-12-11 -- v2.0.8 +- Fixed a crash caused by a reflection call during logging. +- Updating server with the latest parameters and deprecating old ones. + +# 2017-11-27 -- v2.0.7 +- Improve identity reset process, ensuring all information is reset during Identity deletion. + +# 2017-11-06 -- v2.0.6 +- Make token refresh weekly. +- Fixed a crash when performing token operation. + +# 2017-10-11 -- v2.0.5 +- Improved support for working in shared Keychain environments. + +# 2017-09-26 -- v2.0.4 +- Fixed an issue where the FCM token was not associating correctly with an APNs + device token, depending on when the APNs device token was made available. +- Fixed an issue where FCM tokens for different Sender IDs were not associating + correctly with an APNs device token. +- Fixed an issue that was preventing the FCM direct channel from being + established on the first start after 24 hours of being opened. + +# 2017-09-13 -- v2.0.3 +- Fixed a race condition where a token was not being generated on first start, + if Firebase Messaging was included and the app did not register for remote + notifications. + +# 2017-08-25 -- v2.0.2 +- Fixed a startup performance regression, removing a call which was blocking the + main thread. + +# 2017-08-07 -- v2.0.1 +- Fixed issues with token and app identifier being inaccessible when the device + is locked. +- Fixed a crash if bundle identifier is nil, which is possible in some testing + environments. +- Fixed a small memory leak fetching a new token. +- Moved to a new and simplified token storage system. +- Moved to a new queuing system for token fetches and deletes. +- Simplified logic and code around configuration and logging. +- Added clarification about the 'apns_sandbox' parameter, in header comments. + +# 2017-05-08 -- v2.0.0 +- Introduced an improved interface for Swift 3 developers +- Deprecated some methods and properties after moving their logic to the + Firebase Cloud Messaging SDK +- Fixed an intermittent stability issue when a debug build of an app was + replaced with a release build of the same version +- Removed swizzling logic that was sometimes resulting in developers receiving + a validation notice about enabling push notification capabilities, even though + they weren't using push notifications +- Fixed a notification that would sometimes fire twice in quick succession + during the first run of an app + +# 2017-03-31 -- v1.0.10 + +- Improvements to token-fetching logic +- Fixed some warnings in Instance ID +- Improved error messages if Instance ID couldn't be initialized properly +- Improvements to console logging + +# 2017-01-31 -- v1.0.9 + +- Removed an error being mistakenly logged to the console. + +# 2016-07-06 -- v1.0.8 + +- Don't store InstanceID plists in Documents folder. + +# 2016-06-19 -- v1.0.7 + +- Fix remote-notifications warning on app submission. + +# 2016-05-16 -- v1.0.6 + +- Fix CocoaPod linter issues for InstanceID pod. + +# 2016-05-13 -- v1.0.5 + +- Fix Authorization errors for InstanceID tokens. + +# 2016-05-11 -- v1.0.4 + +- Reduce wait for InstanceID token during parallel requests. + +# 2016-04-18 -- v1.0.3 + +- Change flag to disable swizzling to *FirebaseAppDelegateProxyEnabled*. +- Fix incessant Keychain errors while accessing InstanceID. +- Fix max retries for fetching IID token. + +# 2016-04-18 -- v1.0.2 + +- Register for remote notifications on iOS8+ in the SDK itself. diff --git a/Firebase/InstanceID/FIRIMessageCode.h b/Firebase/InstanceID/FIRIMessageCode.h index 80cf0134ddc..579b8b2c56f 100644 --- a/Firebase/InstanceID/FIRIMessageCode.h +++ b/Firebase/InstanceID/FIRIMessageCode.h @@ -65,6 +65,7 @@ typedef NS_ENUM(NSInteger, FIRInstanceIDMessageCode) { kFIRInstanceIDMessageCodeService004 = 7004, kFIRInstanceIDMessageCodeService005 = 7005, kFIRInstanceIDMessageCodeService006 = 7006, + kFIRIntsanceIDInvalidNetworkSession = 7007, // FIRInstanceIDCheckinStore.m // DO NOT USE 8002, 8004 - 8008 kFIRInstanceIDMessageCodeCheckinStore000 = 8000, @@ -76,6 +77,8 @@ typedef NS_ENUM(NSInteger, FIRInstanceIDMessageCode) { kFIRInstanceIDMessageCodeKeyPair002 = 9002, kFIRInstanceIDMessageCodeKeyPairMigrationError = 9004, kFIRInstanceIDMessageCodeKeyPairMigrationSuccess = 9005, + kFIRInstanceIDMessageCodeKeyPairNoLegacyKeyPair = 9006, + // FIRInstanceIDKeyPairStore.m kFIRInstanceIDMessageCodeKeyPairStore000 = 10000, kFIRInstanceIDMessageCodeKeyPairStore001 = 10001, diff --git a/Firebase/InstanceID/FIRInstanceID+Private.h b/Firebase/InstanceID/FIRInstanceID+Private.h index 524441778e2..3e3ec175f9c 100644 --- a/Firebase/InstanceID/FIRInstanceID+Private.h +++ b/Firebase/InstanceID/FIRInstanceID+Private.h @@ -19,8 +19,9 @@ #import "FIRInstanceIDCheckinService.h" /** - * Internal API used by other Firebase SDK teams, including Messaging, Analytics and Remote config. + * Internal API used by Firebase SDK teams by calling in reflection or internal teams. */ +// TODO(chliangGoogle) Rename this to Internal. @interface FIRInstanceID (Private) /** diff --git a/Firebase/InstanceID/FIRInstanceID+Testing.h b/Firebase/InstanceID/FIRInstanceID+Testing.h deleted file mode 100644 index 88fabf216d6..00000000000 --- a/Firebase/InstanceID/FIRInstanceID+Testing.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FIRInstanceID+Private.h" -#import "FIRInstanceID.h" -#import "FIRInstanceIDKeyPairStore.h" -#import "FIRInstanceIDTokenManager.h" - -@interface FIRInstanceID (Testing) - -@property(nonatomic, readwrite, strong) FIRInstanceIDTokenManager *tokenManager; -@property(nonatomic, readwrite, strong) FIRInstanceIDKeyPairStore *keyPairStore; -@property(nonatomic, readwrite, copy) NSString *fcmSenderID; - -/** - * Private initializer. - */ -- (instancetype)initPrivately; - -/** - * Actually makes InstanceID instantiate both the IID and Token-related subsystems. - */ -- (void)start; - -/** - * Without checking any caches etc, always attempts to fetch the default token (unless a fetch - * is already in progress. - */ -- (void)fetchDefaultToken; - -+ (int64_t)maxRetryCountForDefaultToken; -+ (int64_t)minIntervalForDefaultTokenRetry; -+ (int64_t)maxRetryIntervalForDefaultTokenInSeconds; - -@end diff --git a/Firebase/InstanceID/FIRInstanceID.m b/Firebase/InstanceID/FIRInstanceID.m index 20919b65cff..e5b39e344db 100644 --- a/Firebase/InstanceID/FIRInstanceID.m +++ b/Firebase/InstanceID/FIRInstanceID.m @@ -24,6 +24,7 @@ #import #import "FIRInstanceID+Private.h" #import "FIRInstanceIDAuthService.h" +#import "FIRInstanceIDCombinedHandler.h" #import "FIRInstanceIDConstants.h" #import "FIRInstanceIDDefines.h" #import "FIRInstanceIDKeyPairStore.h" @@ -70,7 +71,6 @@ static NSString *const kFIRIIDErrorDomain = @"com.firebase.instanceid"; static NSString *const kFIRIIDServiceInstanceID = @"InstanceID"; -// This should be the same value as FIRErrorCodeInstanceIDFailed, which we can't import directly static NSInteger const kFIRIIDErrorCodeInstanceIDFailed = -121; typedef void (^FIRInstanceIDKeyPairHandler)(FIRInstanceIDKeyPair *keyPair, NSError *error); @@ -114,9 +114,9 @@ @interface FIRInstanceID () @property(nonatomic, readwrite, strong) FIRInstanceIDKeyPairStore *keyPairStore; // backoff and retry for default token -@property(atomic, readwrite, assign) BOOL isFetchingDefaultToken; -@property(atomic, readwrite, assign) BOOL isDefaultTokenFetchScheduled; @property(nonatomic, readwrite, assign) NSInteger retryCountForDefaultToken; +@property(atomic, strong, nullable) + FIRInstanceIDCombinedHandler *defaultTokenFetchHandler; @end @@ -175,10 +175,6 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (void)stopAllRequests { - [self.tokenManager stopAllTokenOperations]; -} - #pragma mark - Tokens - (NSString *)token { @@ -194,7 +190,7 @@ - (NSString *)token { // If we've never had a cached default token, we should fetch one because unrelatedly, // this request will help us determine whether the locally-generated Instance ID keypair is not // unique, and therefore generate a new one. - [self fetchDefaultToken]; + [self defaultTokenWithHandler:nil]; return nil; } } @@ -221,40 +217,16 @@ - (void)instanceIDWithHandler:(FIRInstanceIDResultHandler)handler { // If no handler, simply return since client has generated iid and token. return; } - - // Now get token - FIRInstanceIDTokenHandler tokenHandler = ^void(NSString *token, NSError *error) { - if (error) { - FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID007, - @"Failed to retrieve the default FCM token after %ld retries", - (long)self.retryCountForDefaultToken); - if (handler) { - // If token fetching fails, result should be nil with error returned. + [self defaultTokenWithHandler:^(NSString *_Nullable token, NSError *_Nullable error) { + if (handler) { + if (error) { handler(nil, error); + return; } - return; - } - FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID008, @"Got default token %@", - token); - NSString *previousFCMToken = self.defaultFCMToken; - self.defaultFCMToken = token; - - // Only notify of token refresh if we have a new valid token that's different than before - if (self.defaultFCMToken.length && ![self.defaultFCMToken isEqualToString:previousFCMToken]) { - NSNotification *tokenRefreshNotification = - [NSNotification notificationWithName:kFIRInstanceIDTokenRefreshNotification - object:[self.defaultFCMToken copy]]; - [[NSNotificationQueue defaultQueue] enqueueNotification:tokenRefreshNotification - postingStyle:NSPostASAP]; - } - - if (handler) { result.token = token; handler(result, nil); } - }; - - [self defaultTokenWithHandler:tokenHandler]; + }]; }]; } @@ -517,10 +489,7 @@ - (void)getIDWithHandler:(FIRInstanceIDHandler)handler { // When getID is explicitly called, trigger getToken to make sure token always exists. // This is to avoid ID conflict (ID is not checked for conflict until we generate a token) if (appIdentity) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" [self token]; -#pragma clang diagnostic pop } callHandlerOnMainThread(appIdentity, error); }); @@ -631,7 +600,7 @@ - (void)deleteIdentityWithHandler:(FIRInstanceIDDeleteHandler)handler { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self fetchDefaultToken]; + [self defaultTokenWithHandler:nil]; }); } if (handler) { @@ -731,23 +700,17 @@ - (void)didCompleteConfigure { // Clean up expired tokens by checking the token refresh policy. if ([self.tokenManager checkForTokenRefreshPolicy]) { // Default token is expired, fetch default token from server. - [self fetchDefaultToken]; + [self defaultTokenWithHandler:nil]; } // Notify FCM with the default token. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" self.defaultFCMToken = [self token]; -#pragma clang diagnostic pop } else if ([self isFCMAutoInitEnabled]) { // When there is no cached token, must check auto init is enabled. // If it's disabled, don't initiate token generation/refresh. // If no cache token and auto init is enabled, fetch a token from server. - [self fetchDefaultToken]; + [self defaultTokenWithHandler:nil]; // Notify FCM with the default token. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" self.defaultFCMToken = [self token]; -#pragma clang diagnostic pop } // ONLY checkin when auto data collection is turned on. if ([self isFCMAutoInitEnabled]) { @@ -789,9 +752,8 @@ - (BOOL)isFCMAutoInitEnabled { // Actually makes InstanceID instantiate both the IID and Token-related subsystems. - (void)start { - NSString *instanceIDSubDirectory = kFIRInstanceIDApplicationSupportSubDirectory; - if (![FIRInstanceIDStore hasApplicationSupportSubDirectory:instanceIDSubDirectory]) { - [FIRInstanceIDStore createApplicationSupportSubDirectory:instanceIDSubDirectory]; + if (![FIRInstanceIDStore hasSubDirectory:kFIRInstanceIDSubDirectoryName]) { + [FIRInstanceIDStore createSubDirectory:kFIRInstanceIDSubDirectoryName]; } [self setupTokenManager]; @@ -860,44 +822,30 @@ - (NSInteger)retryIntervalToFetchDefaultToken { kMaxRetryIntervalForDefaultTokenInSeconds); } -- (void)fetchDefaultToken { - if (self.isFetchingDefaultToken) { - return; - } - - FIRInstanceID_WEAKIFY(self); - FIRInstanceIDTokenHandler handler = ^void(NSString *token, NSError *error) { - FIRInstanceID_STRONGIFY(self); +- (void)defaultTokenWithHandler:(nullable FIRInstanceIDTokenHandler)aHandler { + [self defaultTokenWithRetry:NO handler:aHandler]; +} - if (error) { - FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID007, - @"Failed to retrieve the default FCM token after %ld retries", - (long)self.retryCountForDefaultToken); - } else { - FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID008, @"Got default token %@", - token); - NSString *previousFCMToken = self.defaultFCMToken; - self.defaultFCMToken = token; +/** + * @param retry Indicates if the method is called to perform a retry after a failed attempt. + * If `YES`, then actual token request will be performed even if `self.defaultTokenFetchHandler != + * nil` + */ +- (void)defaultTokenWithRetry:(BOOL)retry handler:(nullable FIRInstanceIDTokenHandler)aHandler { + BOOL shouldPerformRequest = retry || self.defaultTokenFetchHandler == nil; - // Only notify of token refresh if we have a new valid token that's different than before - if (self.defaultFCMToken.length && ![self.defaultFCMToken isEqualToString:previousFCMToken]) { - NSNotification *tokenRefreshNotification = - [NSNotification notificationWithName:kFIRInstanceIDTokenRefreshNotification - object:[self.defaultFCMToken copy]]; - [[NSNotificationQueue defaultQueue] enqueueNotification:tokenRefreshNotification - postingStyle:NSPostASAP]; - } - } - }; + if (!self.defaultTokenFetchHandler) { + self.defaultTokenFetchHandler = [[FIRInstanceIDCombinedHandler alloc] init]; + } - // Get a "*" token using this APNS token. - [self defaultTokenWithHandler:handler]; -} + if (aHandler) { + [self.defaultTokenFetchHandler addHandler:aHandler]; + } -- (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler { - if (self.isFetchingDefaultToken || self.isDefaultTokenFetchScheduled) { + if (!shouldPerformRequest) { return; } + NSDictionary *instanceIDOptions = @{}; BOOL hasFirebaseMessaging = NSClassFromString(kFIRInstanceIDFCMSDKClassString) != nil; if (hasFirebaseMessaging && self.apnsTokenData) { @@ -914,7 +862,6 @@ - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler { FIRInstanceID_WEAKIFY(self); FIRInstanceIDTokenHandler newHandler = ^void(NSString *token, NSError *error) { FIRInstanceID_STRONGIFY(self); - self.isFetchingDefaultToken = NO; if (error) { FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID009, @@ -934,23 +881,16 @@ - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler { // Do not retry beyond the maximum limit. if (self.retryCountForDefaultToken < [[self class] maxRetryCountForDefaultToken]) { NSInteger retryInterval = [self retryIntervalToFetchDefaultToken]; - FIRInstanceID_WEAKIFY(self); - self.isDefaultTokenFetchScheduled = YES; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryInterval * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - FIRInstanceID_STRONGIFY(self); - self.isDefaultTokenFetchScheduled = NO; - [self defaultTokenWithHandler:handler]; - }); + [self retryGetDefaultTokenAfter:retryInterval]; } else { - if (handler) { - handler(nil, error); - } + FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID007, + @"Failed to retrieve the default FCM token after %ld retries", + (long)self.retryCountForDefaultToken); + [self performDefaultTokenHandlerWithToken:nil error:error]; } } else { // If somebody updated IID with APNS token while our initial request did not have it // set we need to update it on the server. - BOOL shouldNotifyHandler = YES; NSData *deviceTokenInRequest = instanceIDOptions[kFIRInstanceIDTokenOptionsAPNSKey]; BOOL isSandboxInRequest = [instanceIDOptions[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey] boolValue]; @@ -965,36 +905,65 @@ - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler { if (!APNSRemainedSameDuringFetch && hasFirebaseMessaging) { // APNs value did change mid-fetch, so the token should be re-fetched with the current APNs // value. - self.isDefaultTokenFetchScheduled = YES; - // Wait to notify until we can modify this token with APNS (or receive a new token) - shouldNotifyHandler = NO; - FIRInstanceID_WEAKIFY(self); - dispatch_async(dispatch_get_main_queue(), ^{ - FIRInstanceID_STRONGIFY(self); - self.isDefaultTokenFetchScheduled = NO; - [self defaultTokenWithHandler:handler]; - }); + [self retryGetDefaultTokenAfter:0]; FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeRefetchingTokenForAPNS, @"Received APNS token while fetching default token. " @"Refetching default token."); + // Do not notify and handle completion handler since this is a retry. + // Simply return. + return; } else { FIRInstanceIDLoggerInfo(kFIRInstanceIDMessageCodeInstanceID010, @"Successfully fetched default token."); } // Post the required notifications if somebody is waiting. - if (shouldNotifyHandler && handler) { - handler(token, nil); + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID008, @"Got default token %@", + token); + NSString *previousFCMToken = self.defaultFCMToken; + self.defaultFCMToken = token; + + // Only notify of token refresh if we have a new valid token that's different than before + if (self.defaultFCMToken.length && ![self.defaultFCMToken isEqualToString:previousFCMToken]) { + NSNotification *tokenRefreshNotification = + [NSNotification notificationWithName:kFIRInstanceIDTokenRefreshNotification + object:[self.defaultFCMToken copy]]; + [[NSNotificationQueue defaultQueue] enqueueNotification:tokenRefreshNotification + postingStyle:NSPostASAP]; + + [self performDefaultTokenHandlerWithToken:token error:nil]; } } }; - self.isFetchingDefaultToken = YES; [self tokenWithAuthorizedEntity:self.fcmSenderID scope:kFIRInstanceIDDefaultTokenScope options:instanceIDOptions handler:newHandler]; } +/** + * + */ +- (void)performDefaultTokenHandlerWithToken:(NSString *)token error:(NSError *)error { + if (!self.defaultTokenFetchHandler) { + return; + } + + [self.defaultTokenFetchHandler combinedHandler](token, error); + self.defaultTokenFetchHandler = nil; +} + +- (void)retryGetDefaultTokenAfter:(NSTimeInterval)retryInterval { + FIRInstanceID_WEAKIFY(self); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryInterval * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + FIRInstanceID_STRONGIFY(self); + // Pass nil: no new handlers to be added, currently existing handlers + // will be called + [self defaultTokenWithRetry:YES handler:nil]; + }); +} + #pragma mark - APNS Token // This should only be triggered from FCM. - (void)notifyAPNSTokenIsSet:(NSNotification *)notification { @@ -1045,7 +1014,7 @@ - (void)notifyAPNSTokenIsSet:(NSNotification *)notification { if ([tokenInfo.token isEqualToString:self.defaultFCMToken]) { // We will perform a special fetch for the default FCM token, so that the delegate methods // are called. For all others, we will do an internal re-fetch. - [self fetchDefaultToken]; + [self defaultTokenWithHandler:nil]; } else { [self.tokenManager fetchNewTokenWithAuthorizedEntity:tokenInfo.authorizedEntity scope:tokenInfo.scope diff --git a/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.m b/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.m index ad758dacf7e..f75362fd33e 100644 --- a/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.m +++ b/Firebase/InstanceID/FIRInstanceIDAuthKeyChain.m @@ -33,7 +33,7 @@ @interface FIRInstanceIDAuthKeychain () // cachedKeychainData is keyed by service and account, the value is an array of NSData. // It is used to cache the tokens per service, per account, as well as checkin data per service, // per account inside the keychain. -@property(nonatomic, copy) +@property(nonatomic) NSMutableDictionary *> *> *cachedKeychainData; @@ -89,7 +89,8 @@ - (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NS keychainQuery[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue; keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll; // FIRInstanceIDKeychain should only take a query and return a result, will handle the query here. - CFArrayRef passwordInfos = [[FIRInstanceIDKeychain sharedInstance] itemWithQuery:keychainQuery]; + NSArray *passwordInfos = + CFBridgingRelease([[FIRInstanceIDKeychain sharedInstance] itemWithQuery:keychainQuery]); if (!passwordInfos) { // Nothing was found, simply return from this sync block. @@ -105,19 +106,14 @@ - (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NS } return @[]; } - NSInteger numPasswords = CFArrayGetCount(passwordInfos); + NSInteger numPasswords = passwordInfos.count; results = [[NSMutableArray alloc] init]; - if (0 < numPasswords) { - for (NSUInteger i = 0; i < numPasswords; i++) { - NSDictionary *passwordInfo = [((__bridge NSArray *)passwordInfos) objectAtIndex:i]; - if (passwordInfo[(__bridge id)kSecValueData]) { - [results addObject:passwordInfo[(__bridge id)kSecValueData]]; - } + for (NSUInteger i = 0; i < numPasswords; i++) { + NSDictionary *passwordInfo = [passwordInfos objectAtIndex:i]; + if (passwordInfo[(__bridge id)kSecValueData]) { + [results addObject:passwordInfo[(__bridge id)kSecValueData]]; } } - if (passwordInfos != NULL) { - CFRelease(passwordInfos); - } // We query the keychain because it didn't exist in cache, now query is done, update the result in // the cache. diff --git a/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h b/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h index 810134db8ca..bccaced8903 100644 --- a/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h +++ b/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h @@ -35,12 +35,11 @@ * Init a backup excluded plist file. * * @param fileName The filename for the plist file. - * @param applicationSupportSubDirectory The subdirectory in Application Support to save the plist. + * @param subDirectory The subdirectory in Application Support to save the plist. * * @return Helper which allows to read write data to a backup excluded plist. */ -- (instancetype)initWithFileName:(NSString *)fileName - applicationSupportSubDirectory:(NSString *)applicationSupportSubDirectory; +- (instancetype)initWithFileName:(NSString *)fileName subDirectory:(NSString *)subDirectory; /** * Write dictionary data to the backup excluded plist file. If the file does not exist @@ -79,11 +78,4 @@ */ - (BOOL)doesFileExist; -/** - * Move the plist to Application Support subdirectory. If the plist is already in the correct - * subdirectory this will be a no-op. If the plist was never written to before this will remember - * to write this plist to the Application Support subfolder if it's written to in future. - */ -- (void)moveToApplicationSupportSubDirectory; - @end diff --git a/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.m b/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.m index 487e3f23501..2c322224fa8 100644 --- a/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.m +++ b/Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.m @@ -28,8 +28,8 @@ @interface FIRInstanceIDBackupExcludedPlist () @property(nonatomic, readwrite, copy) NSString *fileName; -@property(nonatomic, readwrite, copy) NSString *applicationSupportSubDirectory; -@property(nonatomic, readwrite, assign) BOOL fileInApplicationSupport; +@property(nonatomic, readwrite, copy) NSString *subDirectoryName; +@property(nonatomic, readwrite, assign) BOOL fileInStandardDirectory; @property(nonatomic, readwrite, strong) NSDictionary *cachedPlistContents; @@ -37,14 +37,18 @@ @interface FIRInstanceIDBackupExcludedPlist () @implementation FIRInstanceIDBackupExcludedPlist -- (instancetype)initWithFileName:(NSString *)fileName - applicationSupportSubDirectory:(NSString *)applicationSupportSubDirectory { +- (instancetype)initWithFileName:(NSString *)fileName subDirectory:(NSString *)subDirectory { self = [super init]; if (self) { _fileName = [fileName copy]; - _applicationSupportSubDirectory = [applicationSupportSubDirectory copy]; - _fileInApplicationSupport = - [self moveToApplicationSupportSubDirectory:applicationSupportSubDirectory]; + _subDirectoryName = [subDirectory copy]; +#if TARGET_OS_IOS + _fileInStandardDirectory = [self moveToApplicationSupportSubDirectory:subDirectory]; +#else + // For tvOS and macOS, we never store the content in document folder, so + // the migration is unnecessary. + _fileInStandardDirectory = YES; +#endif } return self; } @@ -104,14 +108,10 @@ - (NSDictionary *)contentAsDictionary { return self.cachedPlistContents; } -- (void)moveToApplicationSupportSubDirectory { - self.fileInApplicationSupport = - [self moveToApplicationSupportSubDirectory:self.applicationSupportSubDirectory]; -} - - (BOOL)moveToApplicationSupportSubDirectory:(NSString *)subDirectoryName { NSArray *directoryPaths = - NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); + NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES); + // This only going to happen inside iOS so it is an applicationSupportDirectory. NSString *applicationSupportDirPath = directoryPaths.lastObject; NSArray *components = @[ applicationSupportDirPath, subDirectoryName ]; NSString *subDirectoryPath = [NSString pathWithComponents:components]; @@ -151,7 +151,7 @@ - (BOOL)doesFileExist { #pragma mark - Private - (FIRInstanceIDPlistDirectory)plistDirectory { - if (self.fileInApplicationSupport) { + if (_fileInStandardDirectory) { return FIRInstanceIDPlistDirectoryApplicationSupport; } else { return FIRInstanceIDPlistDirectoryDocuments; @@ -176,10 +176,8 @@ - (NSString *)pathWithName:(NSString *)plistName case FIRInstanceIDPlistDirectoryApplicationSupport: directoryPaths = - NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); - components = @[ - directoryPaths.lastObject, self.applicationSupportSubDirectory, plistNameWithExtension - ]; + NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES); + components = @[ directoryPaths.lastObject, _subDirectoryName, plistNameWithExtension ]; break; default: @@ -197,4 +195,12 @@ - (BOOL)doesFileExistInDirectory:(FIRInstanceIDPlistDirectory)directory { return [[NSFileManager defaultManager] fileExistsAtPath:path]; } +- (NSSearchPathDirectory)supportedDirectory { +#if TARGET_OS_TV + return NSCachesDirectory; +#else + return NSApplicationSupportDirectory; +#endif +} + @end diff --git a/Firebase/InstanceID/FIRInstanceIDCheckinService.m b/Firebase/InstanceID/FIRInstanceIDCheckinService.m index a006831c7f7..be5b01a1a46 100644 --- a/Firebase/InstanceID/FIRInstanceIDCheckinService.m +++ b/Firebase/InstanceID/FIRInstanceIDCheckinService.m @@ -72,6 +72,15 @@ - (void)checkinWithExistingCheckin:(FIRInstanceIDCheckinPreferences *)existingCh completion:(FIRInstanceIDDeviceCheckinCompletion)completion { _FIRInstanceIDDevAssert(completion != nil, @"completion required"); + if (self.session == nil) { + FIRInstanceIDLoggerError(kFIRIntsanceIDInvalidNetworkSession, + @"Inconsistent state: NSURLSession has been invalidated"); + NSError *error = + [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn]; + completion(nil, error); + return; + } + NSURL *url = [NSURL URLWithString:kDeviceCheckinURL]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request setValue:@"application/json" forHTTPHeaderField:@"content-type"]; @@ -118,8 +127,9 @@ - (void)checkinWithExistingCheckin:(FIRInstanceIDCheckinPreferences *)existingCh // Somehow the server clock gets out of sync with the device clock. // Reset the last checkin timestamp in case this happens. if (lastCheckinTimestampMillis > currentTimestampMillis) { - FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService002, - @"Invalid last checkin timestamp in future."); + FIRInstanceIDLoggerDebug( + kFIRInstanceIDMessageCodeService002, @"Invalid last checkin timestamp %@ in future.", + [NSDate dateWithTimeIntervalSince1970:lastCheckinTimestampMillis / 1000.0]); lastCheckinTimestampMillis = currentTimestampMillis; } @@ -173,6 +183,8 @@ - (void)checkinWithExistingCheckin:(FIRInstanceIDCheckinPreferences *)existingCh - (void)stopFetching { [self.session invalidateAndCancel]; + // The session cannot be reused after invalidation. Dispose it to prevent accident reusing. + self.session = nil; } #pragma mark - Private diff --git a/Firebase/InstanceID/FIRInstanceIDCheckinStore.h b/Firebase/InstanceID/FIRInstanceIDCheckinStore.h index 2d223834d55..5e1b1194740 100644 --- a/Firebase/InstanceID/FIRInstanceIDCheckinStore.h +++ b/Firebase/InstanceID/FIRInstanceIDCheckinStore.h @@ -37,13 +37,13 @@ extern NSString *const kFIRInstanceIDLegacyCheckinKeychainService; * @param checkinFilename The backup excluded plist filename to persist checkin * preferences. * - * @param applicationSupportSubDirectory Sub-directory in Application support where we write + * @param subDirectoryName Sub-directory in standard directory where we write * InstanceID plist. * * @return Store to persist checkin preferences. */ - (instancetype)initWithCheckinPlistFileName:(NSString *)checkinFilename - applicationSupportSubDirectory:(NSString *)applicationSupportSubDirectory; + subDirectoryName:(NSString *)subDirectoryName; /** * Initialize a checkin store with the given backup excluded plist and keychain. diff --git a/Firebase/InstanceID/FIRInstanceIDCheckinStore.m b/Firebase/InstanceID/FIRInstanceIDCheckinStore.m index aafe3fc9516..fe97440a87e 100644 --- a/Firebase/InstanceID/FIRInstanceIDCheckinStore.m +++ b/Firebase/InstanceID/FIRInstanceIDCheckinStore.m @@ -51,10 +51,10 @@ @interface FIRInstanceIDCheckinStore () @implementation FIRInstanceIDCheckinStore - (instancetype)initWithCheckinPlistFileName:(NSString *)checkinFilename - applicationSupportSubDirectory:(NSString *)applicationSupportSubDirectory { + subDirectoryName:(NSString *)subDirectoryName { FIRInstanceIDBackupExcludedPlist *plist = [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:checkinFilename - applicationSupportSubDirectory:applicationSupportSubDirectory]; + subDirectory:subDirectoryName]; FIRInstanceIDAuthKeychain *keychain = [[FIRInstanceIDAuthKeychain alloc] initWithIdentifier:kFIRInstanceIDCheckinKeychainGeneric]; @@ -108,8 +108,21 @@ - (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences return; } + // Save all other checkin preferences in a plist + NSError *error; + if (![self.plist writeDictionary:checkinPlistContents error:&error]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore003, + @"Failed to save checkin plist contents." + @"Will delete auth credentials"); + [self.keychain removeItemsMatchingService:kFIRInstanceIDCheckinKeychainService + account:self.bundleIdentifierForKeychainAccount + handler:nil]; + if (handler) { + handler(error); + } + return; + } // Save the deviceID and secret in the Keychain - __block BOOL shouldContinue = YES; if (!preferences.hasPreCachedAuthCredentials) { NSData *data = [checkinKeychainContent dataUsingEncoding:NSUTF8StringEncoding]; [self.keychain setData:data @@ -121,30 +134,15 @@ - (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences if (handler) { handler(error); } - shouldContinue = NO; return; } + if (handler) { + handler(nil); + } }]; + } else { + handler(nil); } - if (!shouldContinue) { - return; - } - - // Save all other checkin preferences in a plist - NSError *error; - if (![self.plist writeDictionary:checkinPlistContents error:&error]) { - FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore003, - @"Failed to save checkin plist contents." - @"Will delete auth credentials"); - [self.keychain removeItemsMatchingService:kFIRInstanceIDCheckinKeychainService - account:self.bundleIdentifierForKeychainAccount - handler:nil]; - if (handler) { - handler(error); - } - return; - } - handler(nil); } - (void)removeCheckinPreferencesWithHandler:(void (^)(NSError *error))handler { diff --git a/Firestore/Source/Util/FSTUsageValidation.mm b/Firebase/InstanceID/FIRInstanceIDCombinedHandler.h similarity index 59% rename from Firestore/Source/Util/FSTUsageValidation.mm rename to Firebase/InstanceID/FIRInstanceIDCombinedHandler.h index 93abf878ade..dcb5429b953 100644 --- a/Firestore/Source/Util/FSTUsageValidation.mm +++ b/Firebase/InstanceID/FIRInstanceIDCombinedHandler.h @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google + * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,18 @@ * limitations under the License. */ -#import "Firestore/Source/Util/FSTUsageValidation.h" +#import NS_ASSUME_NONNULL_BEGIN -NSException *FSTInvalidUsage(NSString *exceptionName, NSString *format, ...) { - va_list arg_list; - va_start(arg_list, format); - NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; - va_end(arg_list); +/** + * A generic class to combine several handler blocks into a single block in a thread-safe manner + */ +@interface FIRInstanceIDCombinedHandler : NSObject + +- (void)addHandler:(void (^)(ResultType _Nullable result, NSError* _Nullable error))handler; +- (void (^)(ResultType _Nullable result, NSError* _Nullable error))combinedHandler; - return [[NSException alloc] initWithName:exceptionName reason:formattedString userInfo:nil]; -} +@end NS_ASSUME_NONNULL_END diff --git a/Firebase/InstanceID/FIRInstanceIDCombinedHandler.m b/Firebase/InstanceID/FIRInstanceIDCombinedHandler.m new file mode 100644 index 00000000000..bc6be6c1085 --- /dev/null +++ b/Firebase/InstanceID/FIRInstanceIDCombinedHandler.m @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstanceIDCombinedHandler.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^FIRInstanseIDHandler)(id _Nullable result, NSError *_Nullable error); + +@interface FIRInstanceIDCombinedHandler () +@property(atomic, readonly, strong) NSMutableArray *handlers; +@end + +NS_ASSUME_NONNULL_END + +@implementation FIRInstanceIDCombinedHandler + +- (instancetype)init { + self = [super init]; + if (self) { + _handlers = [NSMutableArray array]; + } + return self; +} + +- (void)addHandler:(FIRInstanseIDHandler)handler { + if (!handler) { + return; + } + + @synchronized(self) { + [self.handlers addObject:handler]; + } +} + +- (FIRInstanseIDHandler)combinedHandler { + FIRInstanseIDHandler combinedHandler = nil; + + @synchronized(self) { + NSArray *handlers = [self.handlers copy]; + combinedHandler = ^(id result, NSError *error) { + for (FIRInstanseIDHandler handler in handlers) { + handler(result, error); + } + }; + } + + return combinedHandler; +} + +@end diff --git a/Firebase/InstanceID/FIRInstanceIDConstants.h b/Firebase/InstanceID/FIRInstanceIDConstants.h index 97f660f6b19..cd2e131551d 100644 --- a/Firebase/InstanceID/FIRInstanceIDConstants.h +++ b/Firebase/InstanceID/FIRInstanceIDConstants.h @@ -43,8 +43,8 @@ FOUNDATION_EXPORT NSString *const kFIRInstanceIDAllScopeIdentifier; /// The scope used to save the IID "*" scope token. FOUNDATION_EXPORT NSString *const kFIRInstanceIDDefaultTokenScope; -/// Subdirectory in Application Support directory to store InstanceID preferences. -FOUNDATION_EXPORT NSString *const kFIRInstanceIDApplicationSupportSubDirectory; +/// Subdirectory in search path directory to store InstanceID preferences. +FOUNDATION_EXPORT NSString *const kFIRInstanceIDSubDirectoryName; /// The key for APNS token in options dictionary. FOUNDATION_EXPORT NSString *const kFIRInstanceIDTokenOptionsAPNSKey; diff --git a/Firebase/InstanceID/FIRInstanceIDConstants.m b/Firebase/InstanceID/FIRInstanceIDConstants.m index b98afcbc753..81f4620e0a4 100644 --- a/Firebase/InstanceID/FIRInstanceIDConstants.m +++ b/Firebase/InstanceID/FIRInstanceIDConstants.m @@ -31,7 +31,7 @@ // Miscellaneous NSString *const kFIRInstanceIDAllScopeIdentifier = @"iid-all"; NSString *const kFIRInstanceIDDefaultTokenScope = @"*"; -NSString *const kFIRInstanceIDApplicationSupportSubDirectory = @"Google/FirebaseInstanceID"; +NSString *const kFIRInstanceIDSubDirectoryName = @"Google/FirebaseInstanceID"; // Registration Options NSString *const kFIRInstanceIDTokenOptionsAPNSKey = @"apns_token"; diff --git a/Firebase/InstanceID/FIRInstanceIDKeyPairStore.m b/Firebase/InstanceID/FIRInstanceIDKeyPairStore.m index 4db201ae7b2..20b5cea6751 100644 --- a/Firebase/InstanceID/FIRInstanceIDKeyPairStore.m +++ b/Firebase/InstanceID/FIRInstanceIDKeyPairStore.m @@ -60,7 +60,7 @@ // Query the key given a tag SecKeyRef FIRInstanceIDCachedKeyRefWithTag(NSString *tag) { _FIRInstanceIDDevAssert([tag length], @"Invalid tag for keychain specified"); - if (![tag length]) { + if (!tag.length) { return NULL; } NSDictionary *queryKey = FIRInstanceIDKeyPairQuery(tag, YES, NO); @@ -130,9 +130,9 @@ - (instancetype)init { self = [super init]; if (self) { NSString *fileName = [[self class] keyStoreFileName]; - _plist = [[FIRInstanceIDBackupExcludedPlist alloc] - initWithFileName:fileName - applicationSupportSubDirectory:kFIRInstanceIDApplicationSupportSubDirectory]; + _plist = + [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:fileName + subDirectory:kFIRInstanceIDSubDirectoryName]; } return self; } @@ -159,7 +159,7 @@ - (BOOL)hasCachedKeyPairs { if ([self cachedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType error:&error] == nil) { if (error) { FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairStore000, - @"Failed to get cached keyPair %@", error); + @"Failed to get the cached keyPair %@", error); } error = nil; [self removeKeyPairCreationTimePlistWithError:&error]; @@ -279,7 +279,9 @@ - (FIRInstanceIDKeyPair *)validCachedKeyPairWithSubtype:(NSString *)subtype // There is no need to reset keypair again here as FIRInstanceID init call is always // going to be ahead of this call, which already trigger keypair reset if it's new install FIRInstanceIDErrorCode code = kFIRInstanceIDErrorCodeInvalidKeyPairCreationTime; - *error = [NSError errorWithFIRInstanceIDErrorCode:code]; + if (error) { + *error = [NSError errorWithFIRInstanceIDErrorCode:code]; + } return nil; } } @@ -314,7 +316,7 @@ + (FIRInstanceIDKeyPair *)keyPairForPrivateKeyTag:(NSString *)privateKeyTag *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeMissingKeyPair]; } FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPair000, - @"No keypair info is retrieved with tag %@", privateKeyTag); + @"No keypair info is found with tag %@", privateKeyTag); return nil; } @@ -342,6 +344,8 @@ - (void)migrateKeyPairCacheIfNeededWithHandler:(void (^)(NSError *error))handler publicKeyTag:legacyPublicKeyTag error:&error]; if (![keyPair isValid]) { + FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairNoLegacyKeyPair, + @"There's no legacy keypair so no need to do migration."); if (handler) { handler(nil); } diff --git a/Firebase/InstanceID/FIRInstanceIDKeychain.m b/Firebase/InstanceID/FIRInstanceIDKeychain.m index c5f1606508d..81c73727e44 100644 --- a/Firebase/InstanceID/FIRInstanceIDKeychain.m +++ b/Firebase/InstanceID/FIRInstanceIDKeychain.m @@ -60,10 +60,9 @@ - (CFTypeRef)itemWithQuery:(NSDictionary *)keychainQuery { if (keyRef) { CFRelease(keyRef); } - FIRInstanceIDLoggerDebug( - kFIRInstanceIDKeychainReadItemError, - @"No info is retrieved from Keychain OSStatus: %d with the keychain query %@", - (int)status, keychainQuery); + FIRInstanceIDLoggerDebug(kFIRInstanceIDKeychainReadItemError, + @"Info is not found in Keychain. OSStatus: %d. Keychain query: %@", + (int)status, keychainQuery); } }); return keyRef; diff --git a/Firebase/InstanceID/FIRInstanceIDStore.h b/Firebase/InstanceID/FIRInstanceIDStore.h index 0e16ca2e404..d1f26348707 100644 --- a/Firebase/InstanceID/FIRInstanceIDStore.h +++ b/Firebase/InstanceID/FIRInstanceIDStore.h @@ -96,8 +96,8 @@ NS_ASSUME_NONNULL_BEGIN * @return The cached token info if any for the given authorizedEntity and scope else * returns nil. */ -- (FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity - scope:(NSString *)scope; +- (nullable FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity + scope:(NSString *)scope; /** * Return all cached token infos from the Keychain. * @@ -153,21 +153,21 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)removeCheckinPreferencesWithHandler:(nullable void (^)(NSError *error))handler; -#pragma mark - ApplicationSupport sub-directory +#pragma mark - Standard Directory sub-directory /** - * Check if Application Support directory has InstanceID subdirectory + * Check if supported directory has InstanceID subdirectory * * @return YES if the Application Support directory has InstanceID subdirectory else NO. */ -+ (BOOL)hasApplicationSupportSubDirectory:(NSString *)subDirectoryName; ++ (BOOL)hasSubDirectory:(NSString *)subDirectoryName; /** * Create InstanceID subdirectory in Application support directory. * * @return YES if the subdirectory was created successfully else NO. */ -+ (BOOL)createApplicationSupportSubDirectory:(NSString *)subDirectoryName; ++ (BOOL)createSubDirectory:(NSString *)subDirectoryName; /** * Removes Application Support subdirectory for InstanceID. @@ -176,7 +176,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return YES if the deletion was successful else NO. */ -+ (BOOL)removeApplicationSupportSubDirectory:(NSString *)subDirectoryName error:(NSError **)error; ++ (BOOL)removeSubDirectory:(NSString *)subDirectoryName error:(NSError **)error; @end diff --git a/Firebase/InstanceID/FIRInstanceIDStore.m b/Firebase/InstanceID/FIRInstanceIDStore.m index f31b5538a87..1c7a0d0ac71 100644 --- a/Firebase/InstanceID/FIRInstanceIDStore.m +++ b/Firebase/InstanceID/FIRInstanceIDStore.m @@ -40,8 +40,8 @@ @implementation FIRInstanceIDStore - (instancetype)initWithDelegate:(NSObject *)delegate { FIRInstanceIDCheckinStore *checkinStore = [[FIRInstanceIDCheckinStore alloc] - initWithCheckinPlistFileName:kCheckinFileName - applicationSupportSubDirectory:kFIRInstanceIDApplicationSupportSubDirectory]; + initWithCheckinPlistFileName:kCheckinFileName + subDirectoryName:kFIRInstanceIDSubDirectoryName]; FIRInstanceIDTokenStore *tokenStore = [FIRInstanceIDTokenStore defaultStore]; @@ -63,8 +63,8 @@ - (instancetype)initWithCheckinStore:(FIRInstanceIDCheckinStore *)checkinStore #pragma mark - Upgrades -+ (BOOL)hasApplicationSupportSubDirectory:(NSString *)subDirectoryName { - NSString *subDirectoryPath = [self pathForApplicationSupportSubDirectory:subDirectoryName]; ++ (BOOL)hasSubDirectory:(NSString *)subDirectoryName { + NSString *subDirectoryPath = [self pathForSupportSubDirectory:subDirectoryName]; BOOL isDirectory; if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath isDirectory:&isDirectory]) { @@ -75,16 +75,24 @@ + (BOOL)hasApplicationSupportSubDirectory:(NSString *)subDirectoryName { return YES; } -+ (NSString *)pathForApplicationSupportSubDirectory:(NSString *)subDirectoryName { ++ (NSSearchPathDirectory)supportedDirectory { +#if TARGET_OS_TV + return NSCachesDirectory; +#else + return NSApplicationSupportDirectory; +#endif +} + ++ (NSString *)pathForSupportSubDirectory:(NSString *)subDirectoryName { NSArray *directoryPaths = - NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); - NSString *applicationSupportDirPath = directoryPaths.lastObject; - NSArray *components = @[ applicationSupportDirPath, subDirectoryName ]; + NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES); + NSString *dirPath = directoryPaths.lastObject; + NSArray *components = @[ dirPath, subDirectoryName ]; return [NSString pathWithComponents:components]; } -+ (BOOL)createApplicationSupportSubDirectory:(NSString *)subDirectoryName { - NSString *subDirectoryPath = [self pathForApplicationSupportSubDirectory:subDirectoryName]; ++ (BOOL)createSubDirectory:(NSString *)subDirectoryName { + NSString *subDirectoryPath = [self pathForSupportSubDirectory:subDirectoryName]; BOOL hasSubDirectory; if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath @@ -109,9 +117,9 @@ + (BOOL)createApplicationSupportSubDirectory:(NSString *)subDirectoryName { return YES; } -+ (BOOL)removeApplicationSupportSubDirectory:(NSString *)subDirectoryName error:(NSError **)error { - if ([self hasApplicationSupportSubDirectory:subDirectoryName]) { - NSString *subDirectoryPath = [self pathForApplicationSupportSubDirectory:subDirectoryName]; ++ (BOOL)removeSubDirectory:(NSString *)subDirectoryName error:(NSError **)error { + if ([self hasSubDirectory:subDirectoryName]) { + NSString *subDirectoryPath = [self pathForSupportSubDirectory:subDirectoryName]; BOOL isDirectory; if ([[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath isDirectory:&isDirectory]) { @@ -145,11 +153,13 @@ - (void)resetCredentialsIfNeeded { if (oldCheckinPreferences) { [self.checkinStore removeCheckinPreferencesWithHandler:^(NSError *error) { if (!error) { - FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeStore002, - @"Removed cached checkin preferences from Keychain."); + FIRInstanceIDLoggerDebug( + kFIRInstanceIDMessageCodeStore002, + @"Removed cached checkin preferences from Keychain because this is a fresh install."); } else { - FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeStore003, - @"Couldn't remove cached checkin preferences. Error: %@", error); + FIRInstanceIDLoggerError( + kFIRInstanceIDMessageCodeStore003, + @"Couldn't remove cached checkin preferences for a fresh install. Error: %@", error); } if (oldCheckinPreferences.deviceID.length && oldCheckinPreferences.secretToken.length) { FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeStore006, diff --git a/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h b/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h index 177ec2d9f31..58368d04172 100644 --- a/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h +++ b/Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @interface FIRInstanceIDTokenDeleteOperation : FIRInstanceIDTokenOperation - (instancetype)initWithAuthorizedEntity:(nullable NSString *)authorizedEntity - scope:(NSString *)scope + scope:(nullable NSString *)scope checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences keyPair:(nullable FIRInstanceIDKeyPair *)keyPair action:(FIRInstanceIDTokenAction)action; diff --git a/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h b/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h index 83ac71411c5..87be60fc061 100644 --- a/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h +++ b/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h @@ -17,6 +17,9 @@ #import "FIRInstanceIDTokenOperation.h" NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT NSString *const kFIRInstanceIDFirebaseUserAgentKey; + @interface FIRInstanceIDTokenFetchOperation : FIRInstanceIDTokenOperation - (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity diff --git a/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.m b/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.m index c2df1f7ed0b..b545c24c3ea 100644 --- a/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.m +++ b/Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.m @@ -25,11 +25,14 @@ #import "FIRInstanceIDUtilities.h" #import "NSError+FIRInstanceID.h" +#import + // We can have a static int since this error should theoretically only // happen once (for the first time). If it repeats there is something // else that is wrong. static int phoneRegistrationErrorRetryCount = 0; static const int kMaxPhoneRegistrationErrorRetryCount = 10; +NSString *const kFIRInstanceIDFirebaseUserAgentKey = @"X-firebase-client"; @implementation FIRInstanceIDTokenFetchOperation @@ -55,6 +58,8 @@ - (void)performTokenOperation { NSMutableURLRequest *request = [[self class] requestWithAuthHeader:authHeader]; NSString *checkinVersionInfo = self.checkinPreferences.versionInfo; [request setValue:checkinVersionInfo forHTTPHeaderField:@"info"]; + [request setValue:[FIRApp firebaseUserAgent] + forHTTPHeaderField:kFIRInstanceIDFirebaseUserAgentKey]; // Build form-encoded body NSString *deviceAuthID = self.checkinPreferences.deviceID; diff --git a/Firebase/InstanceID/FIRInstanceIDTokenOperation.h b/Firebase/InstanceID/FIRInstanceIDTokenOperation.h index 58e0019baa8..1a1842cf287 100644 --- a/Firebase/InstanceID/FIRInstanceIDTokenOperation.h +++ b/Firebase/InstanceID/FIRInstanceIDTokenOperation.h @@ -57,7 +57,7 @@ typedef void (^FIRInstanceIDTokenOperationCompletion)(FIRInstanceIDTokenOperatio @property(nonatomic, readonly) FIRInstanceIDTokenAction action; @property(nonatomic, readonly, nullable) NSString *authorizedEntity; -@property(nonatomic, readonly) NSString *scope; +@property(nonatomic, readonly, nullable) NSString *scope; @property(nonatomic, readonly, nullable) NSDictionary *options; @property(nonatomic, readonly, strong) FIRInstanceIDCheckinPreferences *checkinPreferences; @property(nonatomic, readonly, strong) FIRInstanceIDKeyPair *keyPair; diff --git a/Firebase/InstanceID/Private/FIRInstanceID_Private.h b/Firebase/InstanceID/Private/FIRInstanceID_Private.h new file mode 100644 index 00000000000..6a9e2d34bc6 --- /dev/null +++ b/Firebase/InstanceID/Private/FIRInstanceID_Private.h @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/** + * Private API used by other Firebase SDKs. + */ +@interface FIRInstanceID () + +/** + * Private initializer. + */ +- (nonnull instancetype)initPrivately; + +/** + * Returns a Firebase Messaging scoped token for the firebase app. + * + * @return Returns the stored token if the device has registered with Firebase Messaging, otherwise + * returns nil. + */ +- (nullable NSString *)token; + +@end diff --git a/Firebase/InstanceID/Public/FIRInstanceID.h b/Firebase/InstanceID/Public/FIRInstanceID.h index d95995acfa3..c187d5593cf 100644 --- a/Firebase/InstanceID/Public/FIRInstanceID.h +++ b/Firebase/InstanceID/Public/FIRInstanceID.h @@ -208,14 +208,6 @@ NS_SWIFT_NAME(InstanceID) */ - (void)instanceIDWithHandler:(FIRInstanceIDResultHandler)handler; -/** - * Returns a Firebase Messaging scoped token for the firebase app. - * - * @return Returns the stored token if the device has registered with Firebase Messaging, otherwise - * returns nil. - */ -- (nullable NSString *)token __deprecated_msg("Use instanceIDWithHandler: instead."); - /** * Returns a token that authorizes an Entity (example: cloud service) to perform * an action on behalf of the application identified by Instance ID. diff --git a/Firebase/Messaging/CHANGELOG.md b/Firebase/Messaging/CHANGELOG.md index 8cccf94546b..52fa7f057f9 100644 --- a/Firebase/Messaging/CHANGELOG.md +++ b/Firebase/Messaging/CHANGELOG.md @@ -1,3 +1,17 @@ +# 2019-05-07 -- v4.0.0 +- Remove deprecated `useMessagingDelegateForDirectChannel` property.(#2711) All direct channels (non-APNS) messages will be handled by `messaging:didReceiveMessage:`. Previously in iOS 9 and below, the direct channel messages are handled in `application:didReceiveRemoteNotification:fetchCompletionHandler:` and this behavior can be changed by setting `useMessagingDelegateForDirectChannel` to true. Now that all messages by default are handled in `messaging:didReceiveMessage:`. This boolean value is no longer needed. If you already have set useMessagingDelegateForDirectChannel to YES, or handle all your direct channel messages in `messaging:didReceiveMessage:`. This change should not affect you. +- Remove deprecated API to connect direct channel. (#2717) Should use `shouldEstablishDirectChannel` property instead. +- `GULAppDelegateSwizzler` is used for the app delegate swizzling. (#2683) + +# 2019-04-02 -- v3.5.0 +- Add image support for notification. (#2644) + +# 2019-03-19 -- v3.4.0 +- Adding community support for tvOS. (#2428) + +# 2019-03-05 -- v3.3.2 +- Replaced `NSUserDefaults` with `GULUserDefaults` to avoid potential crashes. (#2443) + # 2019-02-20 -- v3.3.1 - Internal code cleanup. diff --git a/Firebase/Messaging/FIRMMessageCode.h b/Firebase/Messaging/FIRMMessageCode.h index 4b16189affb..4c47838c050 100644 --- a/Firebase/Messaging/FIRMMessageCode.h +++ b/Firebase/Messaging/FIRMMessageCode.h @@ -46,6 +46,7 @@ typedef NS_ENUM(NSInteger, FIRMessagingMessageCode) { kFIRMessagingMessageCodeAPNSTokenNotAvailableDuringTokenFetch = 2022, // I-FCM002022 kFIRMessagingMessageCodeTokenDelegateMethodsNotImplemented = 2023, // I-FCM002023 kFIRMessagingMessageCodeTopicFormatIsDeprecated = 2024, + kFIRMessagingMessageCodeDirectChannelConnectionFailed = 2025, // FIRMessagingClient.m kFIRMessagingMessageCodeClient000 = 4000, // I-FCM004000 kFIRMessagingMessageCodeClient001 = 4001, // I-FCM004001 @@ -188,4 +189,11 @@ typedef NS_ENUM(NSInteger, FIRMessagingMessageCode) { kFIRMessagingMessageCodeAnalyticsInvalidEvent = 19006, // I-FCM019006 kFIRMessagingMessageCodeAnalytics007 = 19007, // I-FCM019007 kFIRMessagingMessageCodeAnalyticsCouldNotInvokeAnalyticsLog = 19008, // I-FCM019008 + + // FIRMessagingExtensionHelper.m + kFIRMessagingServiceExtensionImageInvalidURL = 20000, + kFIRMessagingServiceExtensionImageNotDownloaded = 20001, + kFIRMessagingServiceExtensionLocalFileNotCreated = 20002, + kFIRMessagingServiceExtensionImageNotAttached = 20003, + }; diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m index d360ec0ab2d..7e6653b1551 100644 --- a/Firebase/Messaging/FIRMessaging.m +++ b/Firebase/Messaging/FIRMessaging.m @@ -29,6 +29,7 @@ #import "FIRMessagingContextManagerService.h" #import "FIRMessagingDataMessageManager.h" #import "FIRMessagingDefines.h" +#import "FIRMessagingExtensionHelper.h" #import "FIRMessagingLogger.h" #import "FIRMessagingPubSub.h" #import "FIRMessagingReceiver.h" @@ -46,6 +47,7 @@ #import #import #import +#import #import #import @@ -178,6 +180,15 @@ + (FIRMessaging *)messaging { return messaging; } ++ (FIRMessagingExtensionHelper *)extensionHelper { + static dispatch_once_t once; + static FIRMessagingExtensionHelper *extensionHelper; + dispatch_once(&once, ^{ + extensionHelper = [[FIRMessagingExtensionHelper alloc] init]; + }); + return extensionHelper; +} + - (instancetype)initWithAnalytics:(nullable id)analytics withInstanceID:(FIRInstanceID *)instanceID withUserDefaults:(GULUserDefaults *)defaults { @@ -249,7 +260,7 @@ - (void)configureMessaging:(FIRApp *)app { @"proper integration.", kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey, docsURLString); - [FIRMessagingRemoteNotificationsProxy swizzleMethods]; + [[FIRMessagingRemoteNotificationsProxy sharedProxy] swizzleMethodsIfPossible]; } } @@ -267,7 +278,7 @@ - (void)start { withHost:hostname]; [self.reachability start]; - [self setupApplicationSupportSubDirectory]; + [self setupFileManagerSubDirectory]; // setup FIRMessaging objects [self setupRmqManager]; [self setupClient]; @@ -279,10 +290,9 @@ - (void)start { [self setupNotificationListeners]; } -- (void)setupApplicationSupportSubDirectory { - NSString *messagingSubDirectory = kFIRMessagingApplicationSupportSubDirectory; - if (![[self class] hasApplicationSupportSubDirectory:messagingSubDirectory]) { - [[self class] createApplicationSupportSubDirectory:messagingSubDirectory]; +- (void)setupFileManagerSubDirectory { + if (![[self class] hasSubDirectory:kFIRMessagingSubDirectoryName]) { + [[self class] createSubDirectory:kFIRMessagingSubDirectoryName]; } } @@ -310,7 +320,7 @@ - (void)setupNotificationListeners { } - (void)setupReceiver { - self.receiver = [[FIRMessagingReceiver alloc] initWithUserDefaults:self.messagingUserDefaults]; + self.receiver = [[FIRMessagingReceiver alloc] init]; self.receiver.delegate = self; } @@ -547,10 +557,7 @@ - (void)setAutoInitEnabled:(BOOL)autoInitEnabled { forKey:kFIRMessagingUserDefaultsKeyAutoInitEnabled]; [_messagingUserDefaults synchronize]; if (!isFCMAutoInitEnabled && autoInitEnabled) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" self.defaultFcmToken = self.instanceID.token; -#pragma clang diagnostic pop } } @@ -558,10 +565,7 @@ - (NSString *)FCMToken { NSString *token = self.defaultFcmToken; if (!token) { // We may not have received it from Instance ID yet (via NSNotification), so extract it directly -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" token = self.instanceID.token; -#pragma clang diagnostic pop } return token; } @@ -650,15 +654,6 @@ - (void)notifyDelegateOfFCMTokenAvailability { } } - -- (void)setUseMessagingDelegateForDirectChannel:(BOOL)useMessagingDelegateForDirectChannel { - self.receiver.useDirectChannel = useMessagingDelegateForDirectChannel; -} - -- (BOOL)useMessagingDelegateForDirectChannel { - return self.receiver.useDirectChannel; -} - #pragma mark - Application State Changes - (void)applicationStateChanged { @@ -710,6 +705,9 @@ - (void)updateAutomaticClientConnection { if (!error) { // It means we connected. Fire connection change notification [self notifyOfDirectChannelConnectionChange]; + } else { + FIRMessagingLoggerError(kFIRMessagingMessageCodeDirectChannelConnectionFailed, + @"Failed to connect to direct channel, error: %@\n", error); } }]; } else if (!shouldBeConnected && self.client.isConnected) { @@ -723,33 +721,6 @@ - (void)notifyOfDirectChannelConnectionChange { [center postNotificationName:FIRMessagingConnectionStateChangedNotification object:self]; } -#pragma mark - Connect - -- (void)connectWithCompletion:(FIRMessagingConnectCompletion)handler { - _FIRMessagingDevAssert([NSThread isMainThread], - @"FIRMessaging connect should be called from main thread only."); - _FIRMessagingDevAssert(self.isClientSetup, @"FIRMessaging client not setup."); - [self.client connectWithHandler:^(NSError *error) { - if (handler) { - handler(error); - } - if (!error) { - // It means we connected. Fire connection change notification - [self notifyOfDirectChannelConnectionChange]; - } - }]; - -} - -- (void)disconnect { - _FIRMessagingDevAssert([NSThread isMainThread], - @"FIRMessaging should be called from main thread only."); - if ([self.client isConnected]) { - [self.client disconnect]; - [self notifyOfDirectChannelConnectionChange]; - } -} - #pragma mark - Topics + (NSString *)normalizeTopic:(NSString *)topic { @@ -871,6 +842,7 @@ + (NSString *)FIRMessagingSDKCurrentLocale { - (void)receiver:(FIRMessagingReceiver *)receiver receivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage { if ([self.delegate respondsToSelector:@selector(messaging:didReceiveMessage:)]) { + [self appDidReceiveMessage:remoteMessage.appData]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" [self.delegate messaging:self didReceiveMessage:remoteMessage]; @@ -941,10 +913,7 @@ - (void)didReceiveDefaultInstanceIDToken:(NSNotification *)notification { - (void)defaultInstanceIDTokenWasRefreshed:(NSNotification *)notification { // Retrieve the Instance ID default token, and if it is non-nil, post it -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" NSString *token = self.instanceID.token; -#pragma clang diagnostic pop // Sometimes Instance ID doesn't yet have a token, so wait until the default // token is fetched, and then notify. This ensures that this token should not // be nil when the developer accesses it. @@ -961,8 +930,8 @@ - (void)defaultInstanceIDTokenWasRefreshed:(NSNotification *)notification { #pragma mark - Application Support Directory -+ (BOOL)hasApplicationSupportSubDirectory:(NSString *)subDirectoryName { - NSString *subDirectoryPath = [self pathForApplicationSupportSubDirectory:subDirectoryName]; ++ (BOOL)hasSubDirectory:(NSString *)subDirectoryName { + NSString *subDirectoryPath = [self pathForSubDirectory:subDirectoryName]; BOOL isDirectory; if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath isDirectory:&isDirectory]) { @@ -973,16 +942,16 @@ + (BOOL)hasApplicationSupportSubDirectory:(NSString *)subDirectoryName { return YES; } -+ (NSString *)pathForApplicationSupportSubDirectory:(NSString *)subDirectoryName { - NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, ++ (NSString *)pathForSubDirectory:(NSString *)subDirectoryName { + NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(FIRMessagingSupportedDirectory(), NSUserDomainMask, YES); - NSString *applicationSupportDirPath = directoryPaths.lastObject; - NSArray *components = @[applicationSupportDirPath, subDirectoryName]; + NSString *dirPath = directoryPaths.lastObject; + NSArray *components = @[dirPath, subDirectoryName]; return [NSString pathWithComponents:components]; } -+ (BOOL)createApplicationSupportSubDirectory:(NSString *)subDirectoryName { - NSString *subDirectoryPath = [self pathForApplicationSupportSubDirectory:subDirectoryName]; ++ (BOOL)createSubDirectory:(NSString *)subDirectoryName { + NSString *subDirectoryPath = [self pathForSubDirectory:subDirectoryName]; BOOL hasSubDirectory; if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath diff --git a/Firebase/Messaging/FIRMessagingCheckinService.h b/Firebase/Messaging/FIRMessagingCheckinService.h index 155143a5d55..d7c34512753 100644 --- a/Firebase/Messaging/FIRMessagingCheckinService.h +++ b/Firebase/Messaging/FIRMessagingCheckinService.h @@ -18,7 +18,7 @@ /** * Register the device with Checkin Service and get back the `authID`, `secret token` etc. for the - * client. Checkin results are cached in the `FIRMessagingDefaultsManager` and periodically refreshed to + * client. Checkin results are cached and periodically refreshed to * prevent them from being stale. Each client needs to register with checkin before registering * with FIRMessaging. */ diff --git a/Firebase/Messaging/FIRMessagingConstants.h b/Firebase/Messaging/FIRMessagingConstants.h index ad0d6c90e4c..9a3ce04c863 100644 --- a/Firebase/Messaging/FIRMessagingConstants.h +++ b/Firebase/Messaging/FIRMessagingConstants.h @@ -44,7 +44,7 @@ FOUNDATION_EXPORT NSString *const kFIRMessagingMessageLinkKey; FOUNDATION_EXPORT NSString *const kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey; -FOUNDATION_EXPORT NSString *const kFIRMessagingApplicationSupportSubDirectory; +FOUNDATION_EXPORT NSString *const kFIRMessagingSubDirectoryName; // Notifications FOUNDATION_EXPORT NSString *const kFIRMessagingCheckinFetchedNotification; diff --git a/Firebase/Messaging/FIRMessagingConstants.m b/Firebase/Messaging/FIRMessagingConstants.m index 8904cc59d01..e50c923647b 100644 --- a/Firebase/Messaging/FIRMessagingConstants.m +++ b/Firebase/Messaging/FIRMessagingConstants.m @@ -38,7 +38,7 @@ NSString *const kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey = @"FirebaseAppDelegateProxyEnabled"; -NSString *const kFIRMessagingApplicationSupportSubDirectory = @"Google/FirebaseMessaging"; +NSString *const kFIRMessagingSubDirectoryName = @"Google/FirebaseMessaging"; // Notifications NSString *const kFIRMessagingCheckinFetchedNotification = @"com.google.gcm.notif-checkin-fetched"; diff --git a/Firebase/Messaging/FIRMessagingExtensionHelper.m b/Firebase/Messaging/FIRMessagingExtensionHelper.m new file mode 100644 index 00000000000..7b436e3cb34 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingExtensionHelper.m @@ -0,0 +1,117 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #import "FIRMessagingExtensionHelper.h" + + #import "FIRMMessageCode.h" +#import "FIRMessagingLogger.h" + + static NSString *const kPayloadOptionsName = @"fcm_options"; +static NSString *const kPayloadOptionsImageURLName = @"image"; + + @interface FIRMessagingExtensionHelper () +@property(nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); +@property(nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; + + @end + + @implementation FIRMessagingExtensionHelper + + - (void)populateNotificationContent:(UNMutableNotificationContent *)content + withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler { + self.contentHandler = [contentHandler copy]; + self.bestAttemptContent = content; + + NSString *currentImageURL = content.userInfo[kPayloadOptionsName][kPayloadOptionsImageURLName]; + if (!currentImageURL) { + [self deliverNotification]; + return; + } +#if TARGET_OS_IOS + NSURL *attachmentURL = [NSURL URLWithString:currentImageURL]; + if (attachmentURL) { + [self loadAttachmentForURL:attachmentURL + completionHandler:^(UNNotificationAttachment *attachment) { + self.bestAttemptContent.attachments = @[ attachment ]; + [self deliverNotification]; + }]; + } else { + FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageInvalidURL, + @"The Image URL provided is invalid %@.", currentImageURL); + [self deliverNotification]; + } +#else + [self deliverNotification]; +#endif +} + + #if TARGET_OS_IOS +- (void)loadAttachmentForURL:(NSURL *)attachmentURL + completionHandler:(void (^)(UNNotificationAttachment *))completionHandler { + __block UNNotificationAttachment *attachment = nil; + + NSURLSession *session = [NSURLSession + sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + [[session + downloadTaskWithURL:attachmentURL + completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) { + if (error != nil) { + FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotDownloaded, + @"Failed to download image given URL %@, error: %@\n", + attachmentURL, error); + completionHandler(attachment); + return; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *fileExtension = + [NSString stringWithFormat:@".%@", [response.suggestedFilename pathExtension]]; + NSURL *localURL = [NSURL + fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExtension]]; + [fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error]; + if (error) { + FIRMessagingLoggerError( + kFIRMessagingServiceExtensionLocalFileNotCreated, + @"Failed to move the image file to local location: %@, error: %@\n", localURL, + error); + completionHandler(attachment); + return; + } + + attachment = [UNNotificationAttachment attachmentWithIdentifier:@"" + URL:localURL + options:nil + error:&error]; + if (error) { + FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotAttached, + @"Failed to create attachment with URL %@, error: %@\n", + localURL, error); + completionHandler(attachment); + return; + } + completionHandler(attachment); + }] resume]; +} +#endif + + - (void)deliverNotification { + if (self.contentHandler) { + self.contentHandler(self.bestAttemptContent); + } +} + + @end + diff --git a/Firebase/Messaging/FIRMessagingLogger.m b/Firebase/Messaging/FIRMessagingLogger.m index 62eb8dae33a..d3c96d21135 100644 --- a/Firebase/Messaging/FIRMessagingLogger.m +++ b/Firebase/Messaging/FIRMessagingLogger.m @@ -18,6 +18,8 @@ #import +FIRLoggerService kFIRLoggerMessaging = @"[Firebase/Messaging]"; + @implementation FIRMessagingLogger + (instancetype)standardLogger { diff --git a/Firebase/Messaging/FIRMessagingReceiver.h b/Firebase/Messaging/FIRMessagingReceiver.h index 416ee3b6d69..676c7f1aef2 100644 --- a/Firebase/Messaging/FIRMessagingReceiver.h +++ b/Firebase/Messaging/FIRMessagingReceiver.h @@ -30,15 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @interface FIRMessagingReceiver : NSObject -/// Default initializer for creating the messaging receiver. -- (instancetype)initWithUserDefaults:(GULUserDefaults *)defaults NS_DESIGNATED_INITIALIZER; - -/// Use `initWithUserDefaults:` instead. -- (instancetype)init NS_UNAVAILABLE; - @property(nonatomic, weak, nullable) id delegate; -/// Whether to use direct channel for direct channel message callback handler in all iOS versions. -@property(nonatomic, assign) BOOL useDirectChannel; @end diff --git a/Firebase/Messaging/FIRMessagingReceiver.m b/Firebase/Messaging/FIRMessagingReceiver.m index 0d947c5f082..4d8ec13f4b2 100644 --- a/Firebase/Messaging/FIRMessagingReceiver.m +++ b/Firebase/Messaging/FIRMessagingReceiver.m @@ -18,9 +18,6 @@ #import -#import -#import - #import "FIRMessaging.h" #import "FIRMessagingLogger.h" #import "FIRMessagingUtilities.h" @@ -28,12 +25,6 @@ static NSString *const kUpstreamMessageIDUserInfoKey = @"messageID"; static NSString *const kUpstreamErrorUserInfoKey = @"error"; -/// "Should use Messaging delegate" key stored in NSUserDefaults -NSString *const kFIRMessagingUserDefaultsKeyUseMessagingDelegate = - @"com.firebase.messaging.useMessagingDelegate"; -/// "Should use Messaging Delegate" key stored in Info.plist -NSString *const kFIRMessagingPlistUseMessagingDelegate = - @"FirebaseMessagingUseMessagingDelegateForDirectChannel"; static int downstreamMessageID = 0; @@ -43,16 +34,6 @@ @interface FIRMessagingReceiver () @implementation FIRMessagingReceiver -#pragma mark - Initializer - -- (instancetype)initWithUserDefaults:(GULUserDefaults *)defaults { - self = [super init]; - if (self != nil) { - _defaults = defaults; - } - return self; -} - #pragma mark - FIRMessagingDataMessageManager protocol - (void)didReceiveMessage:(NSDictionary *)message withIdentifier:(nullable NSString *)messageID { @@ -60,14 +41,7 @@ - (void)didReceiveMessage:(NSDictionary *)message withIdentifier:(nullable NSStr messageID = [[self class] nextMessageID]; } - NSInteger majorOSVersion = [[GULAppEnvironmentUtil systemVersion] integerValue]; - if (majorOSVersion >= 10 || self.useDirectChannel) { - // iOS 10 and above or use direct channel is enabled. - [self scheduleIos10NotificationForMessage:message withIdentifier:messageID]; - } else { - // Post notification directly to AppDelegate handlers. This is valid pre-iOS 10. - [self scheduleNotificationForMessage:message]; - } + [self handleDirectChannelMessage:message withIdentifier:messageID]; } - (void)willSendDataMessageWithID:(NSString *)messageID error:(NSError *)error { @@ -112,52 +86,13 @@ - (void)didDeleteMessagesOnServer { } #pragma mark - Private Helpers -// As the new UserNotifications framework in iOS 10 doesn't support constructor/mutation for -// UNNotification object, FCM can't inject the message to the app with UserNotifications framework. -// Define our own protocol, which means app developers need to implement two interfaces to receive -// display notifications and data messages respectively for devices running iOS 10 or above. Devices -// running iOS 9 or below are not affected. -- (void)scheduleIos10NotificationForMessage:(NSDictionary *)message - withIdentifier:(NSString *)messageID { +- (void)handleDirectChannelMessage:(NSDictionary *)message withIdentifier:(NSString *)messageID { FIRMessagingRemoteMessage *wrappedMessage = [[FIRMessagingRemoteMessage alloc] init]; - // TODO: wrap title, body, badge and other fields wrappedMessage.appData = [message copy]; wrappedMessage.messageID = messageID; [self.delegate receiver:self receivedRemoteMessage:wrappedMessage]; } -- (void)scheduleNotificationForMessage:(NSDictionary *)message { - SEL newNotificationSelector = - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - SEL oldNotificationSelector = @selector(application:didReceiveRemoteNotification:); - - dispatch_async(dispatch_get_main_queue(), ^{ - UIApplication *application = FIRMessagingUIApplication(); - if (!application) { - return; - } - id appDelegate = [application delegate]; - if ([appDelegate respondsToSelector:newNotificationSelector]) { - // Try the new remote notification callback - [appDelegate application:application - didReceiveRemoteNotification:message - fetchCompletionHandler:^(UIBackgroundFetchResult result) { - }]; - - } else if ([appDelegate respondsToSelector:oldNotificationSelector]) { - // Try the old remote notification callback -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [appDelegate application:application didReceiveRemoteNotification:message]; -#pragma clang diagnostic pop - } else { - FIRMessagingLoggerError(kFIRMessagingMessageCodeReceiver005, - @"None of the remote notification callbacks implemented by " - @"UIApplicationDelegate"); - } - }); -} - + (NSString *)nextMessageID { @synchronized (self) { ++downstreamMessageID; @@ -165,30 +100,4 @@ + (NSString *)nextMessageID { } } -- (BOOL)useDirectChannel { - // Check storage - id shouldUseMessagingDelegate = - [_defaults objectForKey:kFIRMessagingUserDefaultsKeyUseMessagingDelegate]; - if (shouldUseMessagingDelegate) { - return [shouldUseMessagingDelegate boolValue]; - } - - // Check Info.plist - shouldUseMessagingDelegate = - [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRMessagingPlistUseMessagingDelegate]; - if (shouldUseMessagingDelegate) { - return [shouldUseMessagingDelegate boolValue]; - } - // If none of above exists, we go back to default behavior which is NO. - return NO; -} - -- (void)setUseDirectChannel:(BOOL)useDirectChannel { - BOOL shouldUseMessagingDelegate = [self useDirectChannel]; - if (useDirectChannel != shouldUseMessagingDelegate) { - [_defaults setBool:useDirectChannel forKey:kFIRMessagingUserDefaultsKeyUseMessagingDelegate]; - [_defaults synchronize]; - } -} - @end diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h index 59c3c15d94d..f0010b3b783 100644 --- a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h +++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h @@ -31,10 +31,15 @@ */ + (BOOL)canSwizzleMethods; +/** + * A shared instance of `FIRMessagingRemoteNotificationsProxy` + */ ++ (instancetype)sharedProxy; + /** * Swizzles Application Delegate's remote-notification callbacks and User Notification Center * delegate callback, and invokes the original selectors once done. */ -+ (void)swizzleMethods; +- (void)swizzleMethodsIfPossible; @end diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m index 7cea178edc8..c202e092bc5 100644 --- a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m +++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m @@ -23,23 +23,21 @@ #import "FIRMessagingLogger.h" #import "FIRMessagingUtilities.h" #import "FIRMessaging_Private.h" +#import -static const BOOL kDefaultAutoRegisterEnabledValue = YES; static void * UserNotificationObserverContext = &UserNotificationObserverContext; static NSString *kUserNotificationWillPresentSelectorString = @"userNotificationCenter:willPresentNotification:withCompletionHandler:"; static NSString *kUserNotificationDidReceiveResponseSelectorString = @"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"; -static NSString *kReceiveDataMessageSelectorString = @"messaging:didReceiveMessage:"; -@interface FIRMessagingRemoteNotificationsProxy () +@interface FIRMessagingRemoteNotificationsProxy () @property(strong, nonatomic) NSMutableDictionary *originalAppDelegateImps; @property(strong, nonatomic) NSMutableDictionary *swizzledSelectorsByClass; @property(nonatomic) BOOL didSwizzleMethods; -@property(nonatomic) BOOL didSwizzleAppDelegateMethods; @property(nonatomic) BOOL hasSwizzledUserNotificationDelegate; @property(nonatomic) BOOL isObservingUserNotificationDelegateChanges; @@ -47,24 +45,14 @@ @interface FIRMessagingRemoteNotificationsProxy () @property(strong, nonatomic) id userNotificationCenter; @property(strong, nonatomic) id currentUserNotificationCenterDelegate; +@property(strong, nonatomic) GULAppDelegateInterceptorID appDelegateInterceptorID; + @end @implementation FIRMessagingRemoteNotificationsProxy + (BOOL)canSwizzleMethods { - id canSwizzleValue = - [[NSBundle mainBundle] - objectForInfoDictionaryKey: kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey]; - if (canSwizzleValue && [canSwizzleValue isKindOfClass:[NSNumber class]]) { - NSNumber *canSwizzleNumberValue = (NSNumber *)canSwizzleValue; - return canSwizzleNumberValue.boolValue; - } else { - return kDefaultAutoRegisterEnabledValue; - } -} - -+ (void)swizzleMethods { - [[FIRMessagingRemoteNotificationsProxy sharedProxy] swizzleMethodsIfPossible]; + return [GULAppDelegateSwizzler isAppDelegateProxyEnabled]; } + (instancetype)sharedProxy { @@ -99,12 +87,8 @@ - (void)swizzleMethodsIfPossible { return; } - UIApplication *application = FIRMessagingUIApplication(); - if (!application) { - return; - } - NSObject *appDelegate = [application delegate]; - [self swizzleAppDelegateMethods:appDelegate]; + [GULAppDelegateSwizzler proxyOriginalDelegate]; + self.appDelegateInterceptorID = [GULAppDelegateSwizzler registerAppDelegateInterceptor:self]; // Add KVO listener on [UNUserNotificationCenter currentNotificationCenter]'s delegate property Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter"); @@ -122,6 +106,10 @@ - (void)swizzleMethodsIfPossible { } - (void)unswizzleAllMethods { + if (self.appDelegateInterceptorID) { + [GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:self.appDelegateInterceptorID]; + } + for (NSString *className in self.swizzledSelectorsByClass) { Class klass = NSClassFromString(className); NSArray *selectorStrings = self.swizzledSelectorsByClass[className]; @@ -133,72 +121,6 @@ - (void)unswizzleAllMethods { [self.swizzledSelectorsByClass removeAllObjects]; } -- (void)swizzleAppDelegateMethods:(id)appDelegate { - if (![appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { - return; - } - Class appDelegateClass = [appDelegate class]; - - BOOL didSwizzleAppDelegate = NO; - // Message receiving handler for iOS 9, 8, 7 devices (both display notification and data message). - SEL remoteNotificationSelector = - @selector(application:didReceiveRemoteNotification:); - - SEL remoteNotificationWithFetchHandlerSelector = - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - - // For recording when APNS tokens are registered (or fail to register) - SEL registerForAPNSFailSelector = - @selector(application:didFailToRegisterForRemoteNotificationsWithError:); - - SEL registerForAPNSSuccessSelector = - @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); - - - // Receive Remote Notifications. - BOOL selectorWithFetchHandlerImplemented = NO; - if ([appDelegate respondsToSelector:remoteNotificationWithFetchHandlerSelector]) { - selectorWithFetchHandlerImplemented = YES; - [self swizzleSelector:remoteNotificationWithFetchHandlerSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_appDidReceiveRemoteNotificationWithHandler - inProtocol:@protocol(UIApplicationDelegate)]; - didSwizzleAppDelegate = YES; - } - - if ([appDelegate respondsToSelector:remoteNotificationSelector] || - !selectorWithFetchHandlerImplemented) { - [self swizzleSelector:remoteNotificationSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_appDidReceiveRemoteNotification - inProtocol:@protocol(UIApplicationDelegate)]; - didSwizzleAppDelegate = YES; - } - - // For data message from MCS. - SEL receiveDataMessageSelector = NSSelectorFromString(kReceiveDataMessageSelectorString); - if ([appDelegate respondsToSelector:receiveDataMessageSelector]) { - [self swizzleSelector:receiveDataMessageSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_messagingDidReceiveMessage - inProtocol:@protocol(UIApplicationDelegate)]; - didSwizzleAppDelegate = YES; - } - - // Receive APNS token - [self swizzleSelector:registerForAPNSSuccessSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_appDidRegisterForRemoteNotifications - inProtocol:@protocol(UIApplicationDelegate)]; - - [self swizzleSelector:registerForAPNSFailSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_appDidFailToRegisterForRemoteNotifications - inProtocol:@protocol(UIApplicationDelegate)]; - - self.didSwizzleAppDelegateMethods = didSwizzleAppDelegate; -} - - (void)listenForDelegateChangesInUserNotificationCenter:(id)notificationCenter { Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter"); if (![notificationCenter isKindOfClass:notificationCenterClass]) { @@ -470,39 +392,38 @@ id getNamedPropertyFromObject(id object, NSString *propertyName, Class klass) { return property; } -#pragma mark - Swizzled Methods +#pragma mark - UIApplicationDelegate -void FCM_swizzle_appDidReceiveRemoteNotification(id self, - SEL _cmd, - UIApplication *app, - NSDictionary *userInfo) { +#if TARGET_OS_IOS +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo { [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; - - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, UIApplication *, NSDictionary *))original_imp)(self, - _cmd, - app, - userInfo); - } } +#endif // TARGET_OS_IOS -void FCM_swizzle_appDidReceiveRemoteNotificationWithHandler( - id self, SEL _cmd, UIApplication *app, NSDictionary *userInfo, - void (^handler)(UIBackgroundFetchResult)) { - +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; +} - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, UIApplication *, NSDictionary *, - void (^)(UIBackgroundFetchResult)))original_imp)( - self, _cmd, app, userInfo, handler); - } +- (void)application:(UIApplication *)application +didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + // Pass the APNSToken along to FIRMessaging (and auto-detect the token type) + [FIRMessaging messaging].APNSToken = deviceToken; } +- (void)application:(UIApplication *)application +didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + // Log the fact that we failed to register for remote notifications + FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxyAPNSFailed, + @"Error in " + @"application:didFailToRegisterForRemoteNotificationsWithError: %@", + error.localizedDescription); +} + +#pragma mark - Swizzled Methods + /** * Swizzle the notification handler for iOS 10+ devices. * Signature of original handler is as below: @@ -678,46 +599,4 @@ id userInfoFromNotification(id notification) { return notificationUserInfo; } -void FCM_swizzle_messagingDidReceiveMessage(id self, SEL _cmd, FIRMessaging *message, - FIRMessagingRemoteMessage *remoteMessage) { - [[FIRMessaging messaging] appDidReceiveMessage:remoteMessage.appData]; - - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, FIRMessaging *, FIRMessagingRemoteMessage *))original_imp)( - self, _cmd, message, remoteMessage); - } -} - -void FCM_swizzle_appDidFailToRegisterForRemoteNotifications(id self, - SEL _cmd, - UIApplication *app, - NSError *error) { - // Log the fact that we failed to register for remote notifications - FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxyAPNSFailed, - @"Error in " - @"application:didFailToRegisterForRemoteNotificationsWithError: %@", - error.localizedDescription); - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, UIApplication *, NSError *))original_imp)(self, _cmd, app, error); - } -} - -void FCM_swizzle_appDidRegisterForRemoteNotifications(id self, - SEL _cmd, - UIApplication *app, - NSData *deviceToken) { - // Pass the APNSToken along to FIRMessaging (and auto-detect the token type) - [FIRMessaging messaging].APNSToken = deviceToken; - - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, UIApplication *, NSData *))original_imp)(self, _cmd, app, deviceToken); - } -} - @end diff --git a/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m b/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m index dc6e6c9c9dd..bef6aac5370 100644 --- a/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m +++ b/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m @@ -126,12 +126,16 @@ - (instancetype)initWithDatabaseName:(NSString *)databaseName { self = [super init]; if (self) { _databaseName = [databaseName copy]; +#if TARGET_OS_IOS BOOL didMoveToApplicationSupport = - [self moveToApplicationSupportSubDirectory:kFIRMessagingApplicationSupportSubDirectory]; + [self moveToApplicationSupportSubDirectory:kFIRMessagingSubDirectoryName]; _currentDirectory = didMoveToApplicationSupport ? FIRMessagingRmqDirectoryApplicationSupport : FIRMessagingRmqDirectoryDocuments; +#else + _currentDirectory = FIRMessagingRmqDirectoryApplicationSupport; +#endif [self openDatabase:_databaseName]; } @@ -143,7 +147,7 @@ - (void)dealloc { } - (BOOL)moveToApplicationSupportSubDirectory:(NSString *)subDirectoryName { - NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, + NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(FIRMessagingSupportedDirectory(), NSUserDomainMask, YES); NSString *applicationSupportDirPath = directoryPaths.lastObject; NSArray *components = @[applicationSupportDirPath, subDirectoryName]; @@ -205,12 +209,12 @@ + (NSString *)pathForDatabase:(NSString *)dbName inDirectory:(FIRMessagingRmqDir break; case FIRMessagingRmqDirectoryApplicationSupport: - paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, + paths = NSSearchPathForDirectoriesInDomains(FIRMessagingSupportedDirectory(), NSUserDomainMask, YES); components = @[ paths.lastObject, - kFIRMessagingApplicationSupportSubDirectory, + kFIRMessagingSubDirectoryName, dbNameWithExtension ]; break; @@ -262,10 +266,10 @@ - (void)removeDatabase { + (void)removeDatabase:(NSString *)dbName { NSString *documentsDirPath = [self pathForDatabase:dbName inDirectory:FIRMessagingRmqDirectoryDocuments]; - NSString *applicationSupportDirPath = + NSString *standardDirPath = [self pathForDatabase:dbName inDirectory:FIRMessagingRmqDirectoryApplicationSupport]; [[NSFileManager defaultManager] removeItemAtPath:documentsDirPath error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:applicationSupportDirPath error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:standardDirPath error:nil]; } - (void)openDatabase:(NSString *)dbName { diff --git a/Firebase/Messaging/FIRMessagingSecureSocket.m b/Firebase/Messaging/FIRMessagingSecureSocket.m index 46d4a70be9e..dd78a76d2ab 100644 --- a/Firebase/Messaging/FIRMessagingSecureSocket.m +++ b/Firebase/Messaging/FIRMessagingSecureSocket.m @@ -16,9 +16,9 @@ #import "FIRMessagingSecureSocket.h" -#import "GPBMessage.h" -#import "GPBCodedOutputStream.h" -#import "GPBUtilities.h" +#import +#import +#import #import "FIRMessagingCodedInputStream.h" #import "FIRMessagingDefines.h" diff --git a/Firebase/Messaging/FIRMessagingUtilities.h b/Firebase/Messaging/FIRMessagingUtilities.h index 206ff073f7e..58475bf8877 100644 --- a/Firebase/Messaging/FIRMessagingUtilities.h +++ b/Firebase/Messaging/FIRMessagingUtilities.h @@ -55,4 +55,6 @@ FOUNDATION_EXPORT NSString *FIRMessagingAppIdentifier(void); FOUNDATION_EXPORT uint64_t FIRMessagingGetFreeDiskSpaceInMB(void); FOUNDATION_EXPORT UIApplication *FIRMessagingUIApplication(void); +FOUNDATION_EXPORT NSSearchPathDirectory FIRMessagingSupportedDirectory(void); + diff --git a/Firebase/Messaging/FIRMessagingUtilities.m b/Firebase/Messaging/FIRMessagingUtilities.m index fa3a2334b05..743caed37a2 100644 --- a/Firebase/Messaging/FIRMessagingUtilities.m +++ b/Firebase/Messaging/FIRMessagingUtilities.m @@ -186,3 +186,11 @@ uint64_t FIRMessagingGetFreeDiskSpaceInMB(void) { } return [applicationClass sharedApplication]; } + +NSSearchPathDirectory FIRMessagingSupportedDirectory(void) { +#if TARGET_OS_TV + return NSCachesDirectory; +#else + return NSApplicationSupportDirectory; +#endif +} diff --git a/Firebase/Messaging/Public/FIRMessaging.h b/Firebase/Messaging/Public/FIRMessaging.h index d0ccfacf830..15caf8ebd21 100644 --- a/Firebase/Messaging/Public/FIRMessaging.h +++ b/Firebase/Messaging/Public/FIRMessaging.h @@ -57,23 +57,6 @@ typedef void(^FIRMessagingDeleteFCMTokenCompletion)(NSError * _Nullable error) */ typedef void (^FIRMessagingTopicOperationCompletion)(NSError *_Nullable error); -/** - * The completion handler invoked once the data connection with FIRMessaging is - * established. The data connection is used to send a continuous stream of - * data and all the FIRMessaging data notifications arrive through this connection. - * Once the connection is established we invoke the callback with `nil` error. - * Correspondingly if we get an error while trying to establish a connection - * we invoke the handler with an appropriate error object and do an - * exponential backoff to try and connect again unless successful. - * - * @param error The error object if any describing why the data connection - * to FIRMessaging failed. - */ -typedef void(^FIRMessagingConnectCompletion)(NSError * __nullable error) - NS_SWIFT_NAME(MessagingConnectCompletion) - __deprecated_msg("Please listen for the FIRMessagingConnectionStateChangedNotification " - "NSNotification instead."); - #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 /** * Notification sent when the upstream message has been delivered @@ -245,6 +228,8 @@ NS_SWIFT_NAME(MessagingRemoteMessage) @end @class FIRMessaging; +@class FIRMessagingExtensionHelper; + /** * A protocol to handle token update or data message delivery from FCM. * @@ -264,15 +249,10 @@ NS_SWIFT_NAME(MessagingDelegate) didReceiveRegistrationToken:(NSString *)fcmToken NS_SWIFT_NAME(messaging(_:didReceiveRegistrationToken:)); -/// This method is called on iOS 10+ devices to handle data messages received via FCM -/// direct channel (not via APNS). For iOS 9 and below, the direct channel data message -/// is handled by the UIApplicationDelegate's -application:didReceiveRemoteNotification: method. -/// You can enable all direct channel data messages to be delivered in FIRMessagingDelegate -/// by setting the flag `useMessagingDelegateForDirectMessages` to true. +/// Handle data messages received via FCM direct channel (not via APNS). - (void)messaging:(FIRMessaging *)messaging didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage - NS_SWIFT_NAME(messaging(_:didReceive:)) - __IOS_AVAILABLE(10.0); +NS_SWIFT_NAME(messaging(_:didReceive:)); @end @@ -307,22 +287,6 @@ NS_SWIFT_NAME(Messaging) */ @property(nonatomic, readonly) BOOL isDirectChannelEstablished; -/* - * Whether direct channel message should only use FIRMessagingDelegate messaging(_:didReceive:) - * for message delivery callback. The default value is false. If you need to change - * the default, set FirebaseMessagingUseMessagingDelegateForDirectChannel to true in - * your application’s Info.plist. - * - * If false, the message via direct channel for iOS 9 and below is still delivered in - * `-UIApplicationDelegate application(_:didReceiveRemoteNotification:fetchCompletionHandler:)`, - * and the FIRMessagingRemoteMessage object and its associated data will be unavailable. - * For iOS 10 and above, it is still delivered in `FIRMessagingDelegate messaging(_:didReceive:)`. - * - * If true, the data message sent by direct channel will be delivered via - * `FIRMessagingDelegate messaging(_:didReceive:)` and across all iOS versions. - */ -@property(nonatomic, assign) BOOL useMessagingDelegateForDirectChannel; - /** * FIRMessaging * @@ -330,6 +294,17 @@ NS_SWIFT_NAME(Messaging) */ + (instancetype)messaging NS_SWIFT_NAME(messaging()); +/** + * FIRMessagingExtensionHelper + * + * Use FIRMessagingExtensionHelper to populate rich UI contents for your notifications. + * e.g. If an image URL is set in your notification payload or on the console, call + * FIRMessagingExtensionHelper API to render it on your notification. + * + * @return An instance of FIRMessagingExtensionHelper that handles the extensions API. + */ ++ (FIRMessagingExtensionHelper *)extensionHelper NS_SWIFT_NAME(serviceExtension()) NS_AVAILABLE_IOS(10.0); + /** * Unavailable. Use +messaging instead. */ @@ -434,36 +409,6 @@ NS_SWIFT_NAME(Messaging) completion:(FIRMessagingDeleteFCMTokenCompletion)completion NS_SWIFT_NAME(deleteFCMToken(forSenderID:completion:)); - -#pragma mark - FCM Direct Channel - -/** - * Create a FIRMessaging data connection which will be used to send the data notifications - * sent by your server. It will also be used to send ACKS and other messages based - * on the FIRMessaging ACKS and other messages based on the FIRMessaging protocol. - * - * - * @param handler The handler to be invoked once the connection is established. - * If the connection fails we invoke the handler with an - * appropriate error code letting you know why it failed. At - * the same time, FIRMessaging performs exponential backoff to retry - * establishing a connection and invoke the handler when successful. - */ -- (void)connectWithCompletion:(FIRMessagingConnectCompletion)handler - NS_SWIFT_NAME(connect(handler:)) - __deprecated_msg("Please use the shouldEstablishDirectChannel property instead."); - -/** - * Disconnect the current FIRMessaging data connection. This stops any attempts to - * connect to FIRMessaging. Calling this on an already disconnected client is a no-op. - * - * Call this before `teardown` when your app is going to the background. - * Since the FIRMessaging connection won't be allowed to live when in the background, it is - * prudent to close the connection. - */ -- (void)disconnect - __deprecated_msg("Please use the shouldEstablishDirectChannel property instead."); - #pragma mark - Topics /** diff --git a/Firebase/Messaging/Public/FIRMessagingExtensionHelper.h b/Firebase/Messaging/Public/FIRMessagingExtensionHelper.h new file mode 100644 index 00000000000..f50478ecf82 --- /dev/null +++ b/Firebase/Messaging/Public/FIRMessagingExtensionHelper.h @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +#import +#endif + + NS_ASSUME_NONNULL_BEGIN + + __IOS_AVAILABLE(10.0) +@interface FIRMessagingExtensionHelper : NSObject + + /// Call this API to complete your notification content modification. If you like to +/// overwrite some properties of the content instead of using the default payload, +/// make sure to make your customized motification to the content before passing it to +/// this call. +- (void)populateNotificationContent:(UNMutableNotificationContent *)content + withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler; + + @end + + NS_ASSUME_NONNULL_END diff --git a/Firebase/Messaging/Public/FirebaseMessaging.h b/Firebase/Messaging/Public/FirebaseMessaging.h index ef081c90f55..c5d0bd05046 100755 --- a/Firebase/Messaging/Public/FirebaseMessaging.h +++ b/Firebase/Messaging/Public/FirebaseMessaging.h @@ -15,3 +15,4 @@ */ #import "FIRMessaging.h" +#import "FIRMessagingExtensionHelper.h" diff --git a/Firebase/Storage/FIRStorageReference.m b/Firebase/Storage/FIRStorageReference.m index 78c9dc64dee..8e70aaa2c73 100644 --- a/Firebase/Storage/FIRStorageReference.m +++ b/Firebase/Storage/FIRStorageReference.m @@ -33,7 +33,7 @@ #import #import -#import "GTMSessionFetcherService.h" +#import @implementation FIRStorageReference diff --git a/Firebase/Storage/FIRStorageTask.m b/Firebase/Storage/FIRStorageTask.m index c0848df259d..748bf2e3628 100644 --- a/Firebase/Storage/FIRStorageTask.m +++ b/Firebase/Storage/FIRStorageTask.m @@ -22,7 +22,7 @@ #import "FIRStorageTask_Private.h" #import "FIRStorage_Private.h" -#import "GTMSessionFetcherService.h" +#import @implementation FIRStorageTask diff --git a/Firebase/Storage/FIRStorageUploadTask.m b/Firebase/Storage/FIRStorageUploadTask.m index 0aba3ab431b..994aa21b659 100644 --- a/Firebase/Storage/FIRStorageUploadTask.m +++ b/Firebase/Storage/FIRStorageUploadTask.m @@ -20,7 +20,7 @@ #import "FIRStorageTask_Private.h" #import "FIRStorageUploadTask_Private.h" -#import "GTMSessionUploadFetcher.h" +#import @implementation FIRStorageUploadTask diff --git a/Firebase/Storage/FIRStorageUtils.m b/Firebase/Storage/FIRStorageUtils.m index 66869ecc7b5..c9ccc6f38ac 100644 --- a/Firebase/Storage/FIRStorageUtils.m +++ b/Firebase/Storage/FIRStorageUtils.m @@ -27,7 +27,7 @@ #import "FirebaseStorage.h" -#import "GTMSessionFetcher.h" +#import // This is the list at https://cloud.google.com/storage/docs/json_api/ without &, ; and +. NSString *const kGCSObjectAllowedCharacterSet = diff --git a/FirebaseAnalyticsInterop.podspec b/FirebaseAnalyticsInterop.podspec index bea3f9de282..8f3907c9118 100644 --- a/FirebaseAnalyticsInterop.podspec +++ b/FirebaseAnalyticsInterop.podspec @@ -22,7 +22,7 @@ Pod::Spec.new do |s| } s.social_media_url = 'https://twitter.com/Firebase' s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.10' + s.osx.deployment_target = '10.11' s.tvos.deployment_target = '10.0' s.source_files = 'Interop/Analytics/**/*.h' s.public_header_files = 'Interop/Analytics/Public/*.h' diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 06f09f98838..132174a1e88 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuth' - s.version = '5.4.0' + s.version = '5.4.2' s.summary = 'The official iOS client for Firebase Authentication (plus community support for macOS and tvOS)' s.description = <<-DESC @@ -18,7 +18,7 @@ supports email and password accounts, as well as several 3rd party authenticatio } s.social_media_url = 'https://twitter.com/Firebase' s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.10' + s.osx.deployment_target = '10.11' s.tvos.deployment_target = '10.0' s.cocoapods_version = '>= 1.4.0' @@ -28,7 +28,6 @@ supports email and password accounts, as well as several 3rd party authenticatio source = 'Firebase/Auth/Source/' s.source_files = source + '**/*.[mh]' s.osx.exclude_files = [ - source + '**/FIRAuthAppDelegateProxy.[mh]', source + '**/FIRAuthNotificationManager.[mh]', source + '**/FIRAuthAppCredentialManager.[mh]', source + '**/FIRAuthAPNSTokenManager.[mh]', @@ -63,7 +62,8 @@ supports email and password accounts, as well as several 3rd party authenticatio s.framework = 'Security' s.ios.framework = 'SafariServices' s.dependency 'FirebaseAuthInterop', '~> 1.0' - s.dependency 'FirebaseCore', '~> 5.2' - s.dependency 'GoogleUtilities/Environment', '~> 5.2' + s.dependency 'FirebaseCore', '~> 6.0' + s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 5.8' + s.dependency 'GoogleUtilities/Environment', '~> 5.8' s.dependency 'GTMSessionFetcher/Core', '~> 1.1' end diff --git a/FirebaseAuthInterop.podspec b/FirebaseAuthInterop.podspec index 4ee7710652c..df08e2a64da 100644 --- a/FirebaseAuthInterop.podspec +++ b/FirebaseAuthInterop.podspec @@ -22,7 +22,7 @@ Pod::Spec.new do |s| } s.social_media_url = 'https://twitter.com/Firebase' s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.10' + s.osx.deployment_target = '10.11' s.tvos.deployment_target = '10.0' s.source_files = 'Interop/Auth/**/*.h' s.public_header_files = 'Interop/Auth/Public/*.h' diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index 865632719cc..65364f6fa03 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCore' - s.version = '5.3.1' + s.version = '6.0.0' s.summary = 'Firebase Core for iOS (plus community support for macOS and tvOS)' s.description = <<-DESC @@ -17,7 +17,7 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration } s.social_media_url = 'https://twitter.com/Firebase' s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.10' + s.osx.deployment_target = '10.11' s.tvos.deployment_target = '10.0' s.cocoapods_version = '>= 1.4.0' @@ -28,11 +28,12 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration s.public_header_files = 'Firebase/Core/Public/*.h', 'Firebase/Core/Private/*.h' s.private_header_files = 'Firebase/Core/Private/*.h' s.framework = 'Foundation' + s.dependency 'GoogleUtilities/Environment', '~> 5.2' s.dependency 'GoogleUtilities/Logger', '~> 5.2' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'GCC_PREPROCESSOR_DEFINITIONS' => - 'FIRCore_VERSION=' + s.version.to_s + ' Firebase_VERSION=5.18.0', + 'FIRCore_VERSION=' + s.version.to_s + ' Firebase_VERSION=5.20.0', 'OTHER_CFLAGS' => '-fno-autolink' } end diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec index bed3ef657ab..5804c2637a2 100644 --- a/FirebaseDatabase.podspec +++ b/FirebaseDatabase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDatabase' - s.version = '5.1.0' + s.version = '5.1.1' s.summary = 'Firebase Open Source Libraries for iOS (plus community support for macOS and tvOS)' s.description = <<-DESC @@ -17,7 +17,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel } s.social_media_url = 'https://twitter.com/Firebase' s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.10' + s.osx.deployment_target = '10.11' s.tvos.deployment_target = '10.0' s.cocoapods_version = '>= 1.4.0' @@ -33,7 +33,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel s.frameworks = 'CFNetwork', 'Security', 'SystemConfiguration' s.dependency 'leveldb-library', '~> 1.18' s.dependency 'FirebaseAuthInterop', '~> 1.0' - s.dependency 'FirebaseCore', '~> 5.2' + s.dependency 'FirebaseCore', '~> 6.0' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'GCC_PREPROCESSOR_DEFINITIONS' => diff --git a/FirebaseDynamicLinks.podspec b/FirebaseDynamicLinks.podspec index a5ea8074890..e9ab3b58953 100644 --- a/FirebaseDynamicLinks.podspec +++ b/FirebaseDynamicLinks.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDynamicLinks' - s.version = '3.4.1' + s.version = '3.4.3' s.summary = 'Firebase DynamicLinks for iOS' s.description = <<-DESC @@ -26,8 +26,7 @@ Firebase Dynamic Links are deep links that enhance user experience and increase s.public_header_files = 'Firebase/DynamicLinks/Public/*.h' s.frameworks = 'AssetsLibrary', 'MessageUI', 'QuartzCore' s.weak_framework = 'WebKit' - s.dependency 'FirebaseCore', '~> 5.2' - s.ios.dependency 'FirebaseAnalytics', '~> 5.1' + s.dependency 'FirebaseCore', '~> 6.0' s.dependency 'FirebaseAnalyticsInterop', '~> 1.0' s.pod_target_xcconfig = { diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 8bed72adbd7..522918f1971 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestore' - s.version = '1.0.2' + s.version = '1.2.1' s.summary = 'Google Cloud Firestore for iOS' s.description = <<-DESC @@ -17,7 +17,8 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, } s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.10' + s.osx.deployment_target = '10.11' + s.tvos.deployment_target = '10.0' s.cocoapods_version = '>= 1.4.0' s.static_framework = true @@ -40,6 +41,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, 'Firestore/third_party/Immutable/Tests/**', # Exclude alternate implementations for other platforms + 'Firestore/core/src/firebase/firestore/api/input_validation_std.cc', 'Firestore/core/src/firebase/firestore/remote/connectivity_monitor_noop.cc', 'Firestore/core/src/firebase/firestore/remote/grpc_root_certificate_finder_generated.cc', 'Firestore/core/src/firebase/firestore/util/filesystem_win.cc', @@ -50,14 +52,15 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.public_header_files = 'Firestore/Source/Public/*.h' s.dependency 'FirebaseAuthInterop', '~> 1.0' - s.dependency 'FirebaseCore', '~> 5.2' - s.dependency 'gRPC-C++', '0.0.6' + s.dependency 'FirebaseCore', '~> 6.0' + s.dependency 'gRPC-C++', '0.0.8' s.dependency 'leveldb-library', '~> 1.20' s.dependency 'Protobuf', '~> 3.1' s.dependency 'nanopb', '~> 0.3.901' s.ios.frameworks = 'MobileCoreServices', 'SystemConfiguration' s.osx.frameworks = 'SystemConfiguration' + s.tvos.frameworks = 'SystemConfiguration' s.library = 'c++' s.pod_target_xcconfig = { @@ -95,10 +98,19 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, 'Firestore/third_party/abseil-cpp/**/*.cc' ] ss.exclude_files = [ + # Exclude tests and benchmarks from the framework. 'Firestore/third_party/abseil-cpp/**/*_benchmark.cc', 'Firestore/third_party/abseil-cpp/**/*test*.cc', 'Firestore/third_party/abseil-cpp/absl/hash/internal/print_hash_of.cc', - 'Firestore/third_party/abseil-cpp/absl/synchronization/internal/mutex_nonprod.cc', + + # Avoid the debugging package which uses code that isn't portable to + # ARM (see stack_consumption.cc) and uses syscalls not available on + # tvOS (e.g. sigaltstack). + 'Firestore/third_party/abseil-cpp/absl/debugging/**/*.cc', + + # Exclude the synchronization package because it's dead weight: we don't + # write the kind of heavily threaded code that might benefit from it. + 'Firestore/third_party/abseil-cpp/absl/synchronization/**/*.cc', ] ss.library = 'c++' diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index b365a184714..50cdf7f0852 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFunctions' - s.version = '2.3.0' + s.version = '2.4.0' s.summary = 'Cloud Functions for Firebase iOS SDK.' s.description = <<-DESC @@ -16,6 +16,8 @@ iOS SDK for Cloud Functions for Firebase. } s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.11' + s.tvos.deployment_target = '10.0' s.cocoapods_version = '>= 1.4.0' s.static_framework = true @@ -25,7 +27,7 @@ iOS SDK for Cloud Functions for Firebase. s.public_header_files = 'Functions/FirebaseFunctions/Public/*.h' s.dependency 'FirebaseAuthInterop', '~> 1.0' - s.dependency 'FirebaseCore', '~> 5.2' + s.dependency 'FirebaseCore', '~> 6.0' s.dependency 'GTMSessionFetcher/Core', '~> 1.1' s.pod_target_xcconfig = { diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index cf2740a8bf1..4454018e6e0 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInAppMessaging' - s.version = '0.13.0' + s.version = '0.14.0' s.summary = 'Firebase In-App Messaging for iOS' s.description = <<-DESC @@ -32,8 +32,7 @@ See more product details at https://firebase.google.com/products/in-app-messagin 'FIRInAppMessaging_LIB_VERSION=' + String(s.version) } - s.dependency 'FirebaseCore' - s.ios.dependency 'FirebaseAnalytics' - s.ios.dependency 'FirebaseAnalyticsInterop' - s.dependency 'FirebaseInstanceID' + s.dependency 'FirebaseCore', '~> 6.0' + s.ios.dependency 'FirebaseAnalyticsInterop', '~> 1.2' + s.dependency 'FirebaseInstanceID', '~> 4.0' end diff --git a/FirebaseInAppMessagingDisplay.podspec b/FirebaseInAppMessagingDisplay.podspec index 84ead8247ee..9e19525d964 100644 --- a/FirebaseInAppMessagingDisplay.podspec +++ b/FirebaseInAppMessagingDisplay.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInAppMessagingDisplay' - s.version = '0.13.0' + s.version = '0.13.1' s.summary = 'Firebase In-App Messaging UI for iOS' s.description = <<-DESC @@ -40,6 +40,6 @@ Firebase In-App Messaging SDK. 'FIRInAppMessagingDisplay_LIB_VERSION=' + String(s.version) } - s.dependency 'FirebaseCore' - s.dependency 'FirebaseInAppMessaging', '>=0.12.0' + s.dependency 'FirebaseCore', '~> 6.0' + s.dependency 'FirebaseInAppMessaging', '>=0.14.0' end diff --git a/FirebaseInstanceID.podspec b/FirebaseInstanceID.podspec index d48b224cac2..1f69bc26fef 100644 --- a/FirebaseInstanceID.podspec +++ b/FirebaseInstanceID.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInstanceID' - s.version = '3.7.0' + s.version = '4.0.0' s.summary = 'Firebase InstanceID for iOS' s.description = <<-DESC @@ -28,14 +28,15 @@ services. base_dir = "Firebase/InstanceID/" s.source_files = base_dir + '**/*.[mh]' s.requires_arc = base_dir + '*.m' - s.public_header_files = base_dir + 'Public/*.h' + s.public_header_files = base_dir + 'Public/*.h', base_dir + 'Private/*.h' + s.private_header_files = base_dir + 'Private/*.h' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'GCC_PREPROCESSOR_DEFINITIONS' => 'FIRInstanceID_LIB_VERSION=' + String(s.version) } s.framework = 'Security' - s.dependency 'FirebaseCore', '~> 5.2' + s.dependency 'FirebaseCore', '~> 6.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 5.2' s.dependency 'GoogleUtilities/Environment', '~> 5.2' end diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index 6bbd0999323..d0bcef47fe1 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessaging' - s.version = '3.3.2' + s.version = '3.5.0' s.summary = 'Firebase Messaging for iOS' s.description = <<-DESC @@ -39,10 +39,11 @@ device, and it is completely free. } s.framework = 'SystemConfiguration' s.dependency 'FirebaseAnalyticsInterop', '~> 1.1' - s.dependency 'FirebaseCore', '~> 5.2' - s.dependency 'FirebaseInstanceID', '~> 3.6' - s.dependency 'GoogleUtilities/Reachability', '~> 5.3' - s.dependency 'GoogleUtilities/Environment', '~> 5.3' - s.dependency 'GoogleUtilities/UserDefaults', '~> 5.3' + s.dependency 'FirebaseCore', '~> 6.0' + s.dependency 'FirebaseInstanceID', '~> 4.0' + s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 5.8' + s.dependency 'GoogleUtilities/Reachability', '~> 5.8' + s.dependency 'GoogleUtilities/Environment', '~> 5.8' + s.dependency 'GoogleUtilities/UserDefaults', '~> 5.8' s.dependency 'Protobuf', '~> 3.1' end diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index 71a996c030f..f97fad4d889 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseStorage' - s.version = '3.1.0' + s.version = '3.1.1' s.summary = 'Firebase Storage for iOS (plus community support for macOS and tvOS)' s.description = <<-DESC @@ -17,7 +17,7 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas } s.social_media_url = 'https://twitter.com/Firebase' s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.10' + s.osx.deployment_target = '10.11' s.tvos.deployment_target = '10.0' s.cocoapods_version = '>= 1.4.0' @@ -30,7 +30,7 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas s.osx.framework = 'CoreServices' s.dependency 'FirebaseAuthInterop', '~> 1.0' - s.dependency 'FirebaseCore', '~> 5.2' + s.dependency 'FirebaseCore', '~> 6.0' s.dependency 'GTMSessionFetcher/Core', '~> 1.1' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index ab848534bb9..2ede2f61cfa 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,9 +1,27 @@ # Unreleased -- [changed] Improved performance when querying over documents that contain - subcollections. + +# 1.3.0 +- [feature] You can now query across all collections in your database with a + given collection ID using the `Firestore.collectionGroup()` method. +- [feature] Added community support for tvOS. + +# 1.2.1 +- [fixed] Fixed a use-after-free bug that could be observed when using snapshot + listeners on temporary document references (#2682). + +# 1.2.0 +- [feature] Added community support for macOS (#434). +- [fixed] Fixed the way gRPC certificates are loaded on macOS (#2604). + +# 1.1.0 - [feature] Added `FieldValue.increment()`, which can be used in `updateData(_:)` and `setData(_:merge:)` to increment or decrement numeric field values safely without transactions. +- [changed] Improved performance when querying over documents that contain + subcollections (#2466). +- [changed] Prepared the persistence layer to support collection group queries. + While this feature is not yet available, all schema changes are included + in this release. # v1.0.2 - [changed] Internal improvements. diff --git a/Firestore/Example/App/iOS/Images.xcassets/AppIcon.appiconset/Contents.json b/Firestore/Example/App/iOS/Images.xcassets/AppIcon.appiconset/Contents.json index d7070bc5c02..d8db8d65fd7 100644 --- a/Firestore/Example/App/iOS/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Firestore/Example/App/iOS/Images.xcassets/AppIcon.appiconset/Contents.json @@ -84,10 +84,15 @@ "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } -} +} \ No newline at end of file diff --git a/Firestore/Example/App/macOS_example/AppDelegate.h b/Firestore/Example/App/macOS/AppDelegate.h similarity index 100% rename from Firestore/Example/App/macOS_example/AppDelegate.h rename to Firestore/Example/App/macOS/AppDelegate.h diff --git a/Firestore/Example/App/macOS/AppDelegate.m b/Firestore/Example/App/macOS/AppDelegate.m new file mode 100644 index 00000000000..49f300a8d07 --- /dev/null +++ b/Firestore/Example/App/macOS/AppDelegate.m @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "AppDelegate.h" +#import +#import + +@interface AppDelegate () + +@property(weak) IBOutlet NSWindow *window; +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + +@end diff --git a/Firestore/Example/App/macOS_example/Assets.xcassets/AppIcon.appiconset/Contents.json b/Firestore/Example/App/macOS/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Firestore/Example/App/macOS_example/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Firestore/Example/App/macOS/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Firestore/Example/App/macOS/Assets.xcassets/Contents.json b/Firestore/Example/App/macOS/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/Firestore/Example/App/macOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/macOS_example/Base.lproj/MainMenu.xib b/Firestore/Example/App/macOS/Base.lproj/MainMenu.xib similarity index 98% rename from Firestore/Example/App/macOS_example/Base.lproj/MainMenu.xib rename to Firestore/Example/App/macOS/Base.lproj/MainMenu.xib index 90a42771c8e..ca6b7de54bc 100644 --- a/Firestore/Example/App/macOS_example/Base.lproj/MainMenu.xib +++ b/Firestore/Example/App/macOS/Base.lproj/MainMenu.xib @@ -19,11 +19,11 @@ - + - + - + @@ -37,7 +37,7 @@ - + @@ -55,7 +55,7 @@ - + @@ -668,7 +668,7 @@ - + @@ -678,7 +678,7 @@ - + diff --git a/Firestore/Example/App/macOS_example/Info.plist b/Firestore/Example/App/macOS/Info.plist similarity index 100% rename from Firestore/Example/App/macOS_example/Info.plist rename to Firestore/Example/App/macOS/Info.plist diff --git a/Firestore/Example/App/macOS_example/macOS_example.entitlements b/Firestore/Example/App/macOS/macOS.entitlements similarity index 97% rename from Firestore/Example/App/macOS_example/macOS_example.entitlements rename to Firestore/Example/App/macOS/macOS.entitlements index 40b639e46f5..997a18c9c0a 100644 --- a/Firestore/Example/App/macOS_example/macOS_example.entitlements +++ b/Firestore/Example/App/macOS/macOS.entitlements @@ -3,7 +3,7 @@ com.apple.security.app-sandbox - + com.apple.security.files.user-selected.read-only com.apple.security.network.client diff --git a/Firestore/Example/App/macOS_example/main.m b/Firestore/Example/App/macOS/main.m similarity index 100% rename from Firestore/Example/App/macOS_example/main.m rename to Firestore/Example/App/macOS/main.m diff --git a/Firestore/Example/App/macOS_example/AppDelegate.m b/Firestore/Example/App/macOS_example/AppDelegate.m deleted file mode 100644 index 5a852fd41cc..00000000000 --- a/Firestore/Example/App/macOS_example/AppDelegate.m +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "AppDelegate.h" -#import "FirebaseCore.h" -#import "FirebaseFirestore.h" - -@interface AppDelegate () - -@property(weak) IBOutlet NSWindow *window; -@end - -@implementation AppDelegate - -- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - // create a firestore db - NSString *filePath = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" - ofType:@"plist"]; - FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath]; - [FIRApp configureWithOptions:options]; - FIRFirestore *db = [FIRFirestore firestore]; - - // do the timestamp fix - FIRFirestoreSettings *settings = db.settings; - settings.timestampsInSnapshotsEnabled = true; - db.settings = settings; - - // create a doc - FIRDocumentReference *docRef = [[db collectionWithPath:@"junk"] documentWithPath:@"test_doc"]; - NSDictionary *data = @{@"msg" : @"hello"}; - - [docRef setData:data - completion:^(NSError *_Nullable error) { - if (error != nil) { - NSLog(@"created error: %@", error); - } else { - NSLog(@"Yay!"); - } - }]; -} - -- (void)applicationWillTerminate:(NSNotification *)aNotification { - // Insert code here to tear down your application -} - -@end diff --git a/Firestore/Example/App/tvOS/AppDelegate.h b/Firestore/Example/App/tvOS/AppDelegate.h new file mode 100644 index 00000000000..b0b8e06e84c --- /dev/null +++ b/Firestore/Example/App/tvOS/AppDelegate.h @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface AppDelegate : UIResponder + +@property(strong, nonatomic) UIWindow *window; + +@end diff --git a/Firestore/Example/App/tvOS/AppDelegate.m b/Firestore/Example/App/tvOS/AppDelegate.m new file mode 100644 index 00000000000..4481af28ddb --- /dev/null +++ b/Firestore/Example/App/tvOS/AppDelegate.m @@ -0,0 +1,61 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for + // certain types of temporary interruptions (such as an incoming phone call or SMS message) or + // when the user quits the application and it begins the transition to the background state. Use + // this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. + // Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store + // enough application state information to restore your application to its current state in case + // it is terminated later. If your application supports background execution, this method is + // called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the active state; here you can undo + // many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If + // the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also + // applicationDidEnterBackground:. +} + +@end diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 00000000000..48ecb4fa43e --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "tv" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json new file mode 100644 index 00000000000..d29f024ed5c --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 00000000000..48ecb4fa43e --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "tv" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 00000000000..48ecb4fa43e --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "tv" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 00000000000..16a370df014 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json new file mode 100644 index 00000000000..d29f024ed5c --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 00000000000..16a370df014 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 00000000000..16a370df014 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json new file mode 100644 index 00000000000..db288f368f1 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json @@ -0,0 +1,32 @@ +{ + "assets" : [ + { + "size" : "1280x768", + "idiom" : "tv", + "filename" : "App Icon - App Store.imagestack", + "role" : "primary-app-icon" + }, + { + "size" : "400x240", + "idiom" : "tv", + "filename" : "App Icon.imagestack", + "role" : "primary-app-icon" + }, + { + "size" : "2320x720", + "idiom" : "tv", + "filename" : "Top Shelf Image Wide.imageset", + "role" : "top-shelf-image-wide" + }, + { + "size" : "1920x720", + "idiom" : "tv", + "filename" : "Top Shelf Image.imageset", + "role" : "top-shelf-image" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json new file mode 100644 index 00000000000..7dc95020229 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + }, + { + "idiom" : "tv-marketing", + "scale" : "1x" + }, + { + "idiom" : "tv-marketing", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json new file mode 100644 index 00000000000..7dc95020229 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "tv", + "scale" : "1x" + }, + { + "idiom" : "tv", + "scale" : "2x" + }, + { + "idiom" : "tv-marketing", + "scale" : "1x" + }, + { + "idiom" : "tv-marketing", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Assets.xcassets/Launch Image.launchimage/Contents.json b/Firestore/Example/App/tvOS/Assets.xcassets/Launch Image.launchimage/Contents.json new file mode 100644 index 00000000000..d746a609003 --- /dev/null +++ b/Firestore/Example/App/tvOS/Assets.xcassets/Launch Image.launchimage/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "orientation" : "landscape", + "idiom" : "tv", + "extent" : "full-screen", + "minimum-system-version" : "11.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "tv", + "extent" : "full-screen", + "minimum-system-version" : "9.0", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Firestore/Example/App/tvOS/Base.lproj/Main.storyboard b/Firestore/Example/App/tvOS/Base.lproj/Main.storyboard new file mode 100644 index 00000000000..a5c40f19689 --- /dev/null +++ b/Firestore/Example/App/tvOS/Base.lproj/Main.storyboard @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Firestore/Example/App/tvOS/Info.plist b/Firestore/Example/App/tvOS/Info.plist new file mode 100644 index 00000000000..02942a34f3e --- /dev/null +++ b/Firestore/Example/App/tvOS/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + arm64 + + UIUserInterfaceStyle + Automatic + + diff --git a/Firestore/Example/App/tvOS/ViewController.h b/Firestore/Example/App/tvOS/ViewController.h new file mode 100644 index 00000000000..7f02e1a5218 --- /dev/null +++ b/Firestore/Example/App/tvOS/ViewController.h @@ -0,0 +1,21 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface ViewController : UIViewController + +@end diff --git a/Firestore/Example/App/tvOS/ViewController.m b/Firestore/Example/App/tvOS/ViewController.m new file mode 100644 index 00000000000..5421c791313 --- /dev/null +++ b/Firestore/Example/App/tvOS/ViewController.m @@ -0,0 +1,30 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "ViewController.h" + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. +} + +@end diff --git a/Firestore/Example/App/tvOS/main.m b/Firestore/Example/App/tvOS/main.m new file mode 100644 index 00000000000..efb3cc9e634 --- /dev/null +++ b/Firestore/Example/App/tvOS/main.m @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index e87f93bbc4d..75064ae651e 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -23,17 +23,152 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 0087625FD31D76E1365C589E /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; }; + 00B7AFE2A7C158DD685EB5EE /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; + 01C0A2CF788A93EF2CEB6100 /* memory_index_manager_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73F1F7392210F3D800E1F692 /* memory_index_manager_test.mm */; }; 020AFD89BB40E5175838BB76 /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; + 023829DB2198383927233318 /* FSTLocalSerializerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08A2021552A00B64F25 /* FSTLocalSerializerTests.mm */; }; + 0265CCC8BBB76AE013F52411 /* FSTViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E05E202154B900B64F25 /* FSTViewTests.mm */; }; + 02C953A7B0FA5EF87DB0361A /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; + 036F975093414351FE952F08 /* index_manager_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73F1F73B2210F3D800E1F692 /* index_manager_test.mm */; }; + 0455FC6E2A281BD755FD933A /* precondition_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5520A36E1F00BCEB75 /* precondition_test.cc */; }; + 051D3E20184AF195266EF678 /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; }; 0535C1B65DADAE1CE47FA3CA /* string_format_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */; }; + 056542AD1D0F78E29E22EFA9 /* grpc_connection_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D9649021544D4F00EB9CFB /* grpc_connection_test.cc */; }; + 072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; + 07A64E6C4EB700E3AF3FD496 /* document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908320322E4D00CC290A /* document_test.cc */; }; + 07B1E8C62772758BC82FEBEE /* field_mask_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */; }; + 086E10B1B37666FB746D56BC /* FSTHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03A2021401F00B64F25 /* FSTHelpers.mm */; }; + 08D853C9D3A4DC919C55671A /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; + 08F44F7DF9A3EF0D35C8FB57 /* FIRNumericTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D5B25E7E7D6873CBA4571841 /* FIRNumericTransformTests.mm */; }; + 0963F6D7B0F9AE1E24B82866 /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; }; + 0A6FBE65A7FE048BAD562A15 /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; + 0B7B24194E2131F5C325FE0E /* async_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB467B208E9A8200554BA2 /* async_queue_test.cc */; }; + 0D2D25522A94AA8195907870 /* status.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9920B89AAC00B5BCE7 /* status.pb.cc */; }; + 0D67722B43147F775891EA43 /* FSTSerializerBetaTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C12021557E00B64F25 /* FSTSerializerBetaTests.mm */; }; + 0DAA255C2FEB387895ADEE12 /* bits_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D01201BC69F00D97691 /* bits_test.cc */; }; + 0E4C94369FFF7EC0C9229752 /* iterator_adaptors_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */; }; + 0F54634745BA07B09BDC14D7 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; + 0FA4D5601BE9F0CB5EC2882C /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; + 0FBDD5991E8F6CD5F8542474 /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; }; + 10B7426844685A48234C093B /* FSTArraySortedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF07E1F3D0B6E003D0CDC /* FSTArraySortedDictionaryTests.m */; }; + 10CA552415BE0954221A1626 /* FSTLRUGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CC9650220A0E93200A2D6A1 /* FSTLRUGarbageCollectorTests.mm */; }; + 1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; + 113190791F42202FDE1ABC14 /* FIRQuerySnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */; }; + 11F8EE69182C9699E90A9E3D /* database_info_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D92E20235D22000A432D /* database_info_test.cc */; }; + 12158DFCEE09D24B7988A340 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; + 1291D9F5300AFACD1FBD262D /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; }; + 12BB9ED1CA98AA52B92F497B /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; }; + 12DB753599571E24DCED0C2C /* FIRValidationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06D202154D600B64F25 /* FIRValidationTests.mm */; }; 132E3483789344640A52F223 /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; }; 132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */; }; 132E3EE56C143B2C9ACB6187 /* FSTLevelDBBenchmarkTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E3BB3D5C42282B4ACFB20 /* FSTLevelDBBenchmarkTests.mm */; }; + 135429EEF1D7FA9D1E329392 /* fake_credentials_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = B60894F62170207100EBC644 /* fake_credentials_provider.cc */; }; + 1357806B4CD3A62A8F5DE86D /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; }; + 13D8F4196528BAB19DBB18A7 /* snapshot_version_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */; }; + 152543FD706D5E8851C8DA92 /* precondition_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5520A36E1F00BCEB75 /* precondition_test.cc */; }; + 153F3E4E9E3A0174E29550B4 /* mutation.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */; }; + 156B042479C42DB2C3190C63 /* FSTTreeSortedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0841F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m */; }; + 169D01E6FF2CDF994B32B491 /* create_noop_connectivity_monitor.cc in Sources */ = {isa = PBXBuildFile; fileRef = B67BF448216EB43000CA9097 /* create_noop_connectivity_monitor.cc */; }; + 16F52ECC6FA8A0587CD779EB /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D93220239654000A432D /* user_test.cc */; }; + 16FE432587C1B40AF08613D2 /* type_traits_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A0CF41BA5AED6049B0BEB2C /* type_traits_apple_test.mm */; }; + 1733601ECCEA33E730DEAF45 /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; }; + 17638F813B9B556FE7718C0C /* FIRQuerySnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */; }; + 178FE1E277C63B3E7120BE56 /* watch_change_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B68FC0E421F6848700A7055C /* watch_change_test.mm */; }; + 18638EAED9E126FC5D895B14 /* common.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D221C2DDC800EFB9CC /* common.pb.cc */; }; + 18CF41A17EA3292329E1119D /* FIRGeoPointTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E048202154AA00B64F25 /* FIRGeoPointTests.mm */; }; + 198F193BD9484E49375A7BE7 /* FSTHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03A2021401F00B64F25 /* FSTHelpers.mm */; }; + 1C19D796DB6715368407387A /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; + 1C7254742A9F6F7042C9D78E /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; }; 1CAA9012B25F975D445D5978 /* strerror_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */; }; + 1CC9BABDD52B2A1E37E2698D /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; }; + 1DFAEEE901B4E517A4CB9CAD /* FSTMemoryMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0972021552C00B64F25 /* FSTMemoryMutationQueueTests.mm */; }; + 1E2AE064CF32A604DC7BFD4D /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; + 1E52635E55FD6FAB78FD29D8 /* FSTLevelDBMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0872021552A00B64F25 /* FSTLevelDBMutationQueueTests.mm */; }; + 1EF47EF3D03B0847007C2318 /* xcgmock_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4425A513895DEC60325A139E /* xcgmock_test.mm */; }; + 1F998DDECB54A66222CC66AA /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; }; + 20A26E9D0336F7F32A098D05 /* Pods_Firestore_IntegrationTests_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2220F583583EFC28DE792ABE /* Pods_Firestore_IntegrationTests_tvOS.framework */; }; + 20EC3A46525B8C3471D7D179 /* index_manager_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73F1F73B2210F3D800E1F692 /* index_manager_test.mm */; }; + 215643858470A449D3A3E168 /* stream_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B66D8995213609EE0086DA0C /* stream_test.mm */; }; + 21F821BF241244BA7BF070D9 /* FSTEventManagerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E060202154B900B64F25 /* FSTEventManagerTests.mm */; }; + 227CFA0B2A01884C277E4F1D /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; }; + 229D1A9381F698D71F229471 /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; }; + 239B9B357E67036BEA831E3A /* FSTMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0962021552C00B64F25 /* FSTMutationQueueTests.mm */; }; + 25A75DFA730BAD21A5538EC5 /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D821C2DDC800EFB9CC /* document.pb.cc */; }; + 25CD471A28606A0DEE9F454A /* FSTMutationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B72021555100B64F25 /* FSTMutationTests.mm */; }; + 25FE27330996A59F31713A0C /* FIRDocumentReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E049202154AA00B64F25 /* FIRDocumentReferenceTests.mm */; }; + 26CB3D7C871BC56456C6021E /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; + 27E46C94AAB087C80A97FF7F /* FIRServerTimestampTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */; }; + 28E4B4A53A739AE2C9CF4159 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; + 29FDE0C0BA643E3804D8546C /* FSTMemoryLRUGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CC9650420A0E9BD00A2D6A1 /* FSTMemoryLRUGarbageCollectorTests.mm */; }; + 29FF9029315C3A9FB0E0D79E /* FSTQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E061202154B900B64F25 /* FSTQueryTests.mm */; }; + 2AAEABFD550255271E3BAC91 /* to_string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B68B1E002213A764008977EF /* to_string_apple_test.mm */; }; + 2B1E95FAFD350C191B525F3B /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D93620239689000A432D /* empty_credentials_provider_test.cc */; }; + 2E0BBA7E627EB240BA11B0D0 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; + 2E169CF1E9E499F054BB873A /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; }; + 2E6E6164F44B9E3C6BB88313 /* xcgmock_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4425A513895DEC60325A139E /* xcgmock_test.mm */; }; + 2EAD77559EC654E6CA4D3E21 /* FIRSnapshotMetadataTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */; }; + 2F6E23D7888FC82475C63010 /* token_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */; }; + 300D9D215F4128E69068B863 /* FSTQueryListenerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E05D202154B900B64F25 /* FSTQueryListenerTests.mm */; }; + 3021937CBABFD9270A051900 /* FSTViewSnapshotTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E05C202154B800B64F25 /* FSTViewSnapshotTest.mm */; }; + 31A396C81A107D1DEFDF4A34 /* serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 61F72C5520BC48FD001A68CB /* serializer_test.cc */; }; + 31BDB4CB0E7458C650A77ED0 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; + 31D8E3D925FA3F70AA20ACCE /* FSTMockDatastore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02D20213FFC00B64F25 /* FSTMockDatastore.mm */; }; + 32A95242C56A1A230231DB6A /* testutil.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352820A3B3BD003E0143 /* testutil.cc */; }; 32F022CB75AEE48CDDAF2982 /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; }; 333FCB7BB0C9986B5DF28FC8 /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = B1A7E1959AF8141FA7E6B888 /* grpc_stream_tester.cc */; }; + 34387C13A92D31B212BC0CA9 /* FSTMemoryLRUGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CC9650420A0E9BD00A2D6A1 /* FSTMemoryLRUGarbageCollectorTests.mm */; }; + 355A9171EF3F7AD44A9C60CB /* document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908320322E4D00CC290A /* document_test.cc */; }; + 358DBA8B2560C65D9EB23C35 /* Pods_Firestore_IntegrationTests_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39B832380209CC5BAF93BC52 /* Pods_Firestore_IntegrationTests_macOS.framework */; }; + 36E174A66C323891AEA16A2A /* FIRTimestampTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */; }; 36FD4CE79613D18BC783C55B /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; }; + 37EC6C6EA9169BB99078CA96 /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; }; + 38208AC761FF994BA69822BE /* async_queue_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4681208EA0BE00554BA2 /* async_queue_std_test.cc */; }; + 38430E0E07C54FCD399AE919 /* FIRQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E046202154AA00B64F25 /* FIRQueryTests.mm */; }; + 38F973FA8ADEAFE9541C25EA /* FSTMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0962021552C00B64F25 /* FSTMutationQueueTests.mm */; }; + 392F527F144BADDAC69C5485 /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; }; + 3958F87E768E5CF40B87EF90 /* FSTMemoryLocalStoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0882021552A00B64F25 /* FSTMemoryLocalStoreTests.mm */; }; + 3A7CB01751697ED599F2D9A1 /* executor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4688208F9B9100554BA2 /* executor_test.cc */; }; + 3B47E82ED2A3C59AB5002640 /* FSTMemoryLocalStoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0882021552A00B64F25 /* FSTMemoryLocalStoreTests.mm */; }; 3B843E4C1F3A182900548890 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; }; + 3BCEBA50E9678123245C0272 /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D93620239689000A432D /* empty_credentials_provider_test.cc */; }; + 3D11B104A8F01F85180B38F6 /* field_transform_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352320A3AEC3003E0143 /* field_transform_test.mm */; }; + 3D9619906F09108E34FF0C95 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; + 3DFBA7413965F3E6F366E923 /* grpc_unary_call_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964942163E63900EB9CFB /* grpc_unary_call_test.cc */; }; + 3E0C71810093ADFBAD9B453F /* FSTEventManagerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E060202154B900B64F25 /* FSTEventManagerTests.mm */; }; + 3F2DF1DDDF7F5830F0669992 /* datastore_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 546854A820A36867004BDBD5 /* datastore_test.mm */; }; + 406939B62E5A6A22ADAB6FE6 /* FSTTreeSortedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0841F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m */; }; + 40708C00B429E39CB20BA0F1 /* FIRQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E046202154AA00B64F25 /* FIRQueryTests.mm */; }; + 409C0F2BFC2E1BECFFAC4D32 /* testutil.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352820A3B3BD003E0143 /* testutil.cc */; }; + 41EAC526C543064B8F3F7EDA /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; + 42063E6AE9ADF659AA6D4E18 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; + 4247980BACA0070FB3E4A7A3 /* FSTMemoryRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08C2021552B00B64F25 /* FSTMemoryRemoteDocumentCacheTests.mm */; }; + 45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; + 46999832F7D1709B4C29FAA8 /* FIRDocumentReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E049202154AA00B64F25 /* FIRDocumentReferenceTests.mm */; }; + 470A37727BBF516B05ED276A /* executor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4688208F9B9100554BA2 /* executor_test.cc */; }; + 49C04B97AB282FFA82FD98CD /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; }; + 4A3FF3B16A39A5DC6B7EBA51 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; }; + 4A62B708A6532DD45414DA3A /* sorted_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */; }; + 4A64A339BCA77B9F875D1D8B /* FSTDatastoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07E202154EC00B64F25 /* FSTDatastoreTests.mm */; }; 4AA4ABE36065DB79CD76DD8D /* Pods_Firestore_Benchmarks_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F694C3CE4B77B3C0FA4BBA53 /* Pods_Firestore_Benchmarks_iOS.framework */; }; + 4AD9809C9CE9FA09AC40992F /* async_queue_libdispatch_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4680208EA0BE00554BA2 /* async_queue_libdispatch_test.mm */; }; + 4B52774AABF4ED7C2FA5C1C5 /* FSTMemoryQueryCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08B2021552B00B64F25 /* FSTMemoryQueryCacheTests.mm */; }; + 4B57AE178F715ADE738C4F78 /* field_transform_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352320A3AEC3003E0143 /* field_transform_test.mm */; }; + 4BB325E8B87A2FA4483AA070 /* FSTViewSnapshotTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E05C202154B800B64F25 /* FSTViewSnapshotTest.mm */; }; + 4C10843309CD11C455CF3B2B /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0922021552B00B64F25 /* FSTLevelDBRemoteDocumentCacheTests.mm */; }; + 4CC78CA0E9E03F5DCF13FEBD /* Pods_Firestore_Tests_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7DF4A6F740086A2D8C0E28E /* Pods_Firestore_Tests_tvOS.framework */; }; + 4D1F46B2DD91198C8867C04C /* xcgmock_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4425A513895DEC60325A139E /* xcgmock_test.mm */; }; + 4D42E5C756229C08560DD731 /* XCTestCase+Await.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */; }; + 4D98894EB5B3D778F5628456 /* grpc_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6BBE42F21262CF400C6A53E /* grpc_stream_test.cc */; }; + 4DAF501EE4B4DB79ED4239B0 /* secure_random_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A531FC913E500713A1A /* secure_random_test.cc */; }; + 4DC660A62BC2B6369DA5C563 /* status_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352C20A3B3D7003E0143 /* status_test.cc */; }; + 4E679B9AA80202184D459569 /* FSTDocumentTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B62021555100B64F25 /* FSTDocumentTests.mm */; }; + 4E8085FB9DBE40BAE11F0F4E /* fake_credentials_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = B60894F62170207100EBC644 /* fake_credentials_provider.cc */; }; + 4F67086B5CC1787F612AE503 /* token_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */; }; + 4F857404731D45F02C5EE4C3 /* async_queue_libdispatch_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4680208EA0BE00554BA2 /* async_queue_libdispatch_test.mm */; }; + 535F51F2FF2AB52A6E629091 /* FSTLevelDBMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0872021552A00B64F25 /* FSTLevelDBMutationQueueTests.mm */; }; + 53AB47E44D897C81A94031F6 /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D921C2DDC800EFB9CC /* write.pb.cc */; }; + 54080260D85A6F583E61DA1D /* FSTLocalSerializerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08A2021552A00B64F25 /* FSTLocalSerializerTests.mm */; }; 54131E9720ADE679001DF3FF /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; }; 544129DA21C2DDC800EFB9CC /* common.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D221C2DDC800EFB9CC /* common.pb.cc */; }; 544129DB21C2DDC800EFB9CC /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; @@ -45,6 +180,19 @@ 5467FB01203E5717009C9584 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; 5467FB08203E6A44009C9584 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; }; 546854AA20A36867004BDBD5 /* datastore_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 546854A820A36867004BDBD5 /* datastore_test.mm */; }; + 546877D52248206A005E3DE0 /* collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129C1F315EE100DD57A1 /* collection_spec_test.json */; }; + 546877D62248206A005E3DE0 /* existence_filter_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129D1F315EE100DD57A1 /* existence_filter_spec_test.json */; }; + 546877D72248206A005E3DE0 /* limbo_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */; }; + 546877D82248206A005E3DE0 /* limit_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129F1F315EE100DD57A1 /* limit_spec_test.json */; }; + 546877D92248206A005E3DE0 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; + 546877DA2248206A005E3DE0 /* offline_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A11F315EE100DD57A1 /* offline_spec_test.json */; }; + 546877DB2248206A005E3DE0 /* orderby_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */; }; + 546877DC2248206A005E3DE0 /* perf_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = D5B2593BCB52957D62F1C9D3 /* perf_spec_test.json */; }; + 546877DD2248206A005E3DE0 /* persistence_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A31F315EE100DD57A1 /* persistence_spec_test.json */; }; + 546877DE2248206A005E3DE0 /* query_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 731541602214AFFA0037F4DC /* query_spec_test.json */; }; + 546877DF2248206A005E3DE0 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; }; + 546877E02248206A005E3DE0 /* resume_token_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */; }; + 546877E12248206A005E3DE0 /* write_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A51F315EE100DD57A1 /* write_spec_test.json */; }; 54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A531FC913E500713A1A /* secure_random_test.cc */; }; 54740A581FC914F000713A1A /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; }; 54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; @@ -111,6 +259,9 @@ 5492E0BF2021555100B64F25 /* FSTFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */; }; 5492E0C72021557E00B64F25 /* FSTSerializerBetaTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C12021557E00B64F25 /* FSTSerializerBetaTests.mm */; }; 5492E0C92021557E00B64F25 /* FSTRemoteEventTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C32021557E00B64F25 /* FSTRemoteEventTests.mm */; }; + 5493A424225F9990006DE7BA /* status_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5493A423225F9990006DE7BA /* status_apple_test.mm */; }; + 5493A425225F9990006DE7BA /* status_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5493A423225F9990006DE7BA /* status_apple_test.mm */; }; + 5493A426225F9990006DE7BA /* status_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5493A423225F9990006DE7BA /* status_apple_test.mm */; }; 5495EB032040E90200EBA509 /* CodableGeoPointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */; }; 54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */; }; 549CCA5020A36DBC00BCEB75 /* sorted_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */; }; @@ -124,6 +275,25 @@ 54A0352F20A3B3D8003E0143 /* status_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352C20A3B3D7003E0143 /* status_test.cc */; }; 54A0353020A3B3D8003E0143 /* statusor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352D20A3B3D7003E0143 /* statusor_test.cc */; }; 54A0353520A3D8CB003E0143 /* iterator_adaptors_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */; }; + 54AA3393224BF935006CE580 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AA3392224BF935006CE580 /* AppDelegate.m */; }; + 54AA3396224BF935006CE580 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AA3395224BF935006CE580 /* ViewController.m */; }; + 54AA3399224BF935006CE580 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54AA3397224BF935006CE580 /* Main.storyboard */; }; + 54AA339B224BF936006CE580 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 54AA339A224BF936006CE580 /* Assets.xcassets */; }; + 54AA339E224BF936006CE580 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AA339D224BF936006CE580 /* main.m */; }; + 54ACB6C9224C11F400172E69 /* collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129C1F315EE100DD57A1 /* collection_spec_test.json */; }; + 54ACB6CA224C11F400172E69 /* existence_filter_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129D1F315EE100DD57A1 /* existence_filter_spec_test.json */; }; + 54ACB6CB224C11F400172E69 /* limbo_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */; }; + 54ACB6CC224C11F400172E69 /* limit_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129F1F315EE100DD57A1 /* limit_spec_test.json */; }; + 54ACB6CD224C11F400172E69 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; + 54ACB6CE224C11F400172E69 /* offline_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A11F315EE100DD57A1 /* offline_spec_test.json */; }; + 54ACB6CF224C11F400172E69 /* orderby_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */; }; + 54ACB6D0224C11F400172E69 /* perf_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = D5B2593BCB52957D62F1C9D3 /* perf_spec_test.json */; }; + 54ACB6D1224C11F400172E69 /* persistence_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A31F315EE100DD57A1 /* persistence_spec_test.json */; }; + 54ACB6D2224C11F400172E69 /* query_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 731541602214AFFA0037F4DC /* query_spec_test.json */; }; + 54ACB6D3224C11F400172E69 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; }; + 54ACB6D4224C11F400172E69 /* resume_token_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */; }; + 54ACB6D5224C11F400172E69 /* write_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A51F315EE100DD57A1 /* write_spec_test.json */; }; + 54ACB6D6224C125B00172E69 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 54D400D32148BACE001D2BCC /* GoogleService-Info.plist */; }; 54C2294F1FECABAE007D065B /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; }; 54D400D42148BACE001D2BCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 54D400D32148BACE001D2BCC /* GoogleService-Info.plist */; }; 54DA12A61F315EE100DD57A1 /* collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129C1F315EE100DD57A1 /* collection_spec_test.json */; }; @@ -137,11 +307,32 @@ 54DA12AE1F315EE100DD57A1 /* resume_token_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */; }; 54DA12AF1F315EE100DD57A1 /* write_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A51F315EE100DD57A1 /* write_spec_test.json */; }; 54EB764D202277B30088B8F3 /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; }; + 550FB7562D0CF9C3E1984000 /* FSTQueryListenerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E05D202154B900B64F25 /* FSTQueryListenerTests.mm */; }; + 5556B648B9B1C2F79A706B4F /* common.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D221C2DDC800EFB9CC /* common.pb.cc */; }; + 55BDA39A16C4229A1AECB796 /* FSTLevelDBLRUGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CC9650620A0E9C600A2D6A1 /* FSTLevelDBLRUGarbageCollectorTests.mm */; }; + 5686B35D611C1CFF6BFE7215 /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D9342023966E000A432D /* credentials_provider_test.cc */; }; + 568EC1C0F68A7B95E57C8C6C /* leveldb_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */; }; + 56D85436D3C864B804851B15 /* string_format_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */; }; + 58E377DCCC64FE7D2C6B59A1 /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; }; + 596C782EFB68131380F8EEF8 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D93220239654000A432D /* user_test.cc */; }; + 59D1E0A722CE68E00A3F85AA /* FSTLevelDBTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */; }; 5A080105CCBFDB6BF3F3772D /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; }; + 5B62003FEA9A3818FDF4E2DD /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; }; + 5BE49546D57C43DDFCDB6FBD /* to_string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B68B1E002213A764008977EF /* to_string_apple_test.mm */; }; + 5C7FAF228D0F52CFFE9E41B5 /* transform_operations_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352220A3AEC3003E0143 /* transform_operations_test.mm */; }; 5CC9650320A0E93200A2D6A1 /* FSTLRUGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CC9650220A0E93200A2D6A1 /* FSTLRUGarbageCollectorTests.mm */; }; 5CC9650520A0E9BD00A2D6A1 /* FSTMemoryLRUGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CC9650420A0E9BD00A2D6A1 /* FSTMemoryLRUGarbageCollectorTests.mm */; }; 5CC9650720A0E9C600A2D6A1 /* FSTLevelDBLRUGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CC9650620A0E9C600A2D6A1 /* FSTLevelDBLRUGarbageCollectorTests.mm */; }; 5D405BE298CE4692CB00790A /* Pods_Firestore_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */; }; + 5D45CC300ED037358EF33A8F /* snapshot_version_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */; }; + 5D5E24E3FA1128145AA117D2 /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; }; + 5E5B3B8B3A41C8EB70035A6B /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; + 5E6F9184B271F6D5312412FF /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; }; + 5EFBAD082CB0F86CD0711979 /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; }; + 5F05A801B1EA44BC1264E55A /* FIRTypeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E071202154D600B64F25 /* FIRTypeTests.mm */; }; + 5F19F66D8B01BA2B97579017 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; + 5FA3DB52A478B01384D3A2ED /* query.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D621C2DDC800EFB9CC /* query.pb.cc */; }; + 5FE047FE866758FD6A6A6478 /* FIRFieldPathTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */; }; 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; }; 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; @@ -154,6 +345,8 @@ 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; + 60985657831B8DDE2C65AC8B /* FIRFieldsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06A202154D500B64F25 /* FIRFieldsTests.mm */; }; + 60C72F86D2231B1B6592A5E6 /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; 6161B5032047140C00A99DBB /* FIRFirestoreSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */; }; 618BBEA620B89AAC00B5BCE7 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; }; 618BBEA720B89AAC00B5BCE7 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; @@ -162,21 +355,121 @@ 618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; 618BBEB020B89AAC00B5BCE7 /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; }; 618BBEB120B89AAC00B5BCE7 /* status.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9920B89AAC00B5BCE7 /* status.pb.cc */; }; + 61D1EB3438B92F61F6CAC191 /* FSTRemoteEventTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C32021557E00B64F25 /* FSTRemoteEventTests.mm */; }; 61F72C5620BC48FD001A68CB /* serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 61F72C5520BC48FD001A68CB /* serializer_test.cc */; }; + 627253FDEC6BB5549FE77F4E /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; + 62DA31B79FE97A90EEF28B0B /* delayed_constructor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D0A6E9136804A41CEC9D55D4 /* delayed_constructor_test.cc */; }; + 63BB61B6366E7F80C348419D /* FSTLevelDBTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */; }; + 660E99DEDA0A6FC1CCB200F9 /* FIRArrayTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */; }; + 6672B445E006A7708B8531ED /* FSTImmutableSortedDictionary+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0801F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m */; }; + 66FAB8EAC012A3822BD4D0C9 /* leveldb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */; }; + 69ED7BC38B3F981DE91E7933 /* strerror_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */; }; + 6A4F6B42C628D55CCE0C311F /* FIRQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E069202154D500B64F25 /* FIRQueryTests.mm */; }; + 6B8806528FD3757D33D8B8AE /* FSTMemoryQueryCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08B2021552B00B64F25 /* FSTMemoryQueryCacheTests.mm */; }; + 6DCA8E54E652B78EFF3EEDAC /* XCTestCase+Await.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */; }; + 6E4854B19B120C6F0F8192CC /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; 6E59498D20F55BA800ECD9A5 /* FuzzingResources in Resources */ = {isa = PBXBuildFile; fileRef = 6ED6DEA120F5502700FC6076 /* FuzzingResources */; }; 6E8302E021022309003E1EA3 /* FSTFuzzTestFieldPath.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6E8302DF21022309003E1EA3 /* FSTFuzzTestFieldPath.mm */; }; 6EA39FDE20FE820E008D461F /* FSTFuzzTestSerializer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EA39FDD20FE820E008D461F /* FSTFuzzTestSerializer.mm */; }; + 6EB896CD1B64A60E6C82D8CC /* xcgmock_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4425A513895DEC60325A139E /* xcgmock_test.mm */; }; + 6EC28BB8C38E3FD126F68211 /* delayed_constructor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D0A6E9136804A41CEC9D55D4 /* delayed_constructor_test.cc */; }; 6EDD3B4620BF247500C33877 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6EDD3B4820BF247500C33877 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 6EDD3B4920BF247500C33877 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; 6EDD3B6020BF25AE00C33877 /* FSTFuzzTestsPrincipal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EDD3B5E20BF24D000C33877 /* FSTFuzzTestsPrincipal.mm */; }; + 6EEA00A737690EF82A3C91C6 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; }; 6F3CAC76D918D6B0917EDF92 /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B9C261C26C5D311E1E3C0CB9 /* query_test.cc */; }; + 6FD2369F24E884A9D767DD80 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; + 6FF2B680CC8631B06C7BD7AB /* FSTMemorySpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */; }; + 70D96C9129976DB01AC58BAC /* FSTMemoryMutationQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0972021552C00B64F25 /* FSTMemoryMutationQueueTests.mm */; }; 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; }; + 71DF9A27169F25383C762F85 /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = B1A7E1959AF8141FA7E6B888 /* grpc_stream_tester.cc */; }; + 72AD91671629697074F2545B /* ordered_code_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D03201BC6E400D97691 /* ordered_code_test.cc */; }; + 731541612214AFFA0037F4DC /* query_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 731541602214AFFA0037F4DC /* query_spec_test.json */; }; + 736C4E82689F1CA1859C4A3F /* XCTestCase+Await.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */; }; 73866AA12082B0A5009BB4FF /* FIRArrayTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */; }; + 73E42D984FB36173A2BDA57C /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; }; + 73F1F73C2210F3D800E1F692 /* memory_index_manager_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73F1F7392210F3D800E1F692 /* memory_index_manager_test.mm */; }; + 73F1F73D2210F3D800E1F692 /* index_manager_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73F1F73B2210F3D800E1F692 /* index_manager_test.mm */; }; + 73F1F7412211FEF300E1F692 /* leveldb_index_manager_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73F1F7402211FEF300E1F692 /* leveldb_index_manager_test.mm */; }; 73FE5066020EF9B2892C86BF /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; + 7400AC9377419A28B782B5EC /* objc_compatibility_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B696858F221770F000271095 /* objc_compatibility_apple_test.mm */; }; + 7495E3BAE536CD839EE20F31 /* FSTLevelDBSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */; }; + 74985DE2C7EF4150D7A455FD /* statusor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352D20A3B3D7003E0143 /* statusor_test.cc */; }; + 75D124966E727829A5F99249 /* FIRTypeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E071202154D600B64F25 /* FIRTypeTests.mm */; }; + 78E38BEDF502B85E27D50C3B /* nanopb_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 353EEE078EF3F39A9B7279F6 /* nanopb_string_test.cc */; }; + 7A3BE0ED54933C234FDE23D1 /* leveldb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */; }; + 7A7EC216A0015D7620B4FF3E /* string_format_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */; }; + 7B8D7BAC1A075DB773230505 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; }; + 7BBE0389D855242DDB83334B /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = B1A7E1959AF8141FA7E6B888 /* grpc_stream_tester.cc */; }; + 7DBE7DB90CF83B589A94980F /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; }; + 7DD67E9621C52B790E844B16 /* FIRDatabaseTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06C202154D500B64F25 /* FIRDatabaseTests.mm */; }; + 7E4218DB09B85F8E379C73CB /* FSTLevelDBQueryCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0982021552C00B64F25 /* FSTLevelDBQueryCacheTests.mm */; }; + 7EF540911720DAAF516BEDF0 /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B9C261C26C5D311E1E3C0CB9 /* query_test.cc */; }; + 804B0C6CCE3933CF3948F249 /* grpc_streaming_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964922154AB8F00EB9CFB /* grpc_streaming_reader_test.cc */; }; + 81B23D2D4E061074958AF12F /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; }; + 81D1B1D2B66BD8310AC5707F /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; }; + 8388418F43042605FB9BFB92 /* testutil.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352820A3B3BD003E0143 /* testutil.cc */; }; + 83A9CD3B6E791A860CE81FA1 /* async_queue_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4681208EA0BE00554BA2 /* async_queue_std_test.cc */; }; + 8403D519C916C72B9C7F2FA1 /* FIRValidationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06D202154D600B64F25 /* FIRValidationTests.mm */; }; + 8413BD9958F6DD52C466D70F /* sorted_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */; }; + 8460C97C9209D7DAF07090BD /* FIRFieldsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06A202154D500B64F25 /* FIRFieldsTests.mm */; }; 84DBE646DCB49305879D3500 /* nanopb_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 353EEE078EF3F39A9B7279F6 /* nanopb_string_test.cc */; }; + 85B8918FC8C5DC62482E39C3 /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; }; + 8612F3C7E4A7D17221442699 /* grpc_unary_call_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964942163E63900EB9CFB /* grpc_unary_call_test.cc */; }; + 862B1AC9EDAB309BBF4FB18C /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; }; + 8683BBC3AC7B01937606A83B /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; + 86E6FC2B7657C35B342E1436 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; }; + 8705C4856498F66E471A0997 /* FIRWriteBatchTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06F202154D600B64F25 /* FIRWriteBatchTests.mm */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; + 88FD82A1FC5FEC5D56B481D8 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; + 8943A7C0750CEB0B98D21209 /* FSTPersistenceTestHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08D2021552B00B64F25 /* FSTPersistenceTestHelpers.mm */; }; + 897F3C1936612ACB018CA1DD /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; }; + 8A0749707105A077728119C2 /* FSTImmutableSortedDictionary+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0801F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m */; }; + 8A6C809B9F81C30B7333FCAA /* FIRFirestoreSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */; }; + 8C39F6D4B3AA9074DF00CFB8 /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; }; 8C82D4D3F9AB63E79CC52DC8 /* Pods_Firestore_IntegrationTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */; }; + 8DA258092DD856D829D973B5 /* transform_operations_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352220A3AEC3003E0143 /* transform_operations_test.mm */; }; + 8F3AE423677A4C50F7E0E5C0 /* database_info_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D92E20235D22000A432D /* database_info_test.cc */; }; + 8F4F40E9BC7ED588F67734D5 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; }; + 900D0E9F18CE3DB954DD0D1E /* async_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB467B208E9A8200554BA2 /* async_queue_test.cc */; }; + 904DA0AE915C02154AE547FC /* FSTLocalStoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0832021552A00B64F25 /* FSTLocalStoreTests.mm */; }; + 927D22C6D294B82D1580C48D /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0922021552B00B64F25 /* FSTLevelDBRemoteDocumentCacheTests.mm */; }; + 9328C93759C78A10FDBF68E0 /* FSTLocalStoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0832021552A00B64F25 /* FSTLocalStoreTests.mm */; }; + 938F2AF6EC5CD0B839300DB0 /* query.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D621C2DDC800EFB9CC /* query.pb.cc */; }; + 939A15D3AD941CF7242DA9FA /* FSTLevelDBLRUGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CC9650620A0E9C600A2D6A1 /* FSTLevelDBLRUGarbageCollectorTests.mm */; }; + 939C898FE9D129F6A2EA259C /* FSTHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03A2021401F00B64F25 /* FSTHelpers.mm */; }; + 94E5399FA5EA82CCB0549AB5 /* FSTDocumentKeyTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B22021555000B64F25 /* FSTDocumentKeyTests.mm */; }; + 95ED06D2B0078D3CDB821B68 /* FIRArrayTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */; }; + 9664E5831CE35D515CDBC12A /* FSTRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E09C2021552D00B64F25 /* FSTRemoteDocumentCacheTests.mm */; }; + 9720B8BD354CCB64C0C627E6 /* nanopb_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 353EEE078EF3F39A9B7279F6 /* nanopb_string_test.cc */; }; + 974FF09E6AFD24D5A39B898B /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; + 9774A6C2AA02A12D80B34C3C /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; }; + 9783FAEA4CF758E8C4C2D76E /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; }; + 9794E074439ABE5457E60F35 /* xcgmock_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4425A513895DEC60325A139E /* xcgmock_test.mm */; }; + 9A29D572C64CA1FA62F591D4 /* FIRQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E069202154D500B64F25 /* FIRQueryTests.mm */; }; + 9AC28D928902C6767A11F5FC /* type_traits_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A0CF41BA5AED6049B0BEB2C /* type_traits_apple_test.mm */; }; + 9BD7DC8F5ADA0FE64AFAFA75 /* FSTLRUGarbageCollectorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CC9650220A0E93200A2D6A1 /* FSTLRUGarbageCollectorTests.mm */; }; + 9D0E720F5A6DBD48FF325016 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; + 9D71628E38D9F64C965DF29E /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; + A17DBC8F24127DA8A381F865 /* testutil.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352820A3B3BD003E0143 /* testutil.cc */; }; + A1F57CC739211F64F2E9232D /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; + A38F4AE525A87FDEA41DED47 /* FSTLevelDBQueryCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0982021552C00B64F25 /* FSTLevelDBQueryCacheTests.mm */; }; + A4ECA8335000CBDF94586C94 /* FSTDatastoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07E202154EC00B64F25 /* FSTDatastoreTests.mm */; }; + A55266E6C986251D283CE948 /* FIRCursorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E070202154D600B64F25 /* FIRCursorTests.mm */; }; + A57EC303CD2D6AA4F4745551 /* FIRFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */; }; + A5AB1815C45FFC762981E481 /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D921C2DDC800EFB9CC /* write.pb.cc */; }; + A61AE3D94C975A87EFA82ADA /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; }; + A64B1CD2776BC118C74503A7 /* leveldb_index_manager_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73F1F7402211FEF300E1F692 /* leveldb_index_manager_test.mm */; }; + A6543DD0A56F8523C6D518E1 /* FSTMutationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B72021555100B64F25 /* FSTMutationTests.mm */; }; + A6D29E15ED1221352DBE0CF2 /* FSTPersistenceTestHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08D2021552B00B64F25 /* FSTPersistenceTestHelpers.mm */; }; + A7470B7B2433264FFDCC7AC3 /* FSTLevelDBLocalStoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08F2021552B00B64F25 /* FSTLevelDBLocalStoreTests.mm */; }; + A8C9FF6D13E6C83D4AB54EA7 /* secure_random_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A531FC913E500713A1A /* secure_random_test.cc */; }; + A907244EE37BC32C8D82948E /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; + A94884460990CD48CC0AD070 /* xcgmock_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4425A513895DEC60325A139E /* xcgmock_test.mm */; }; + AAA50E56B9A7EF3EFDA62172 /* create_noop_connectivity_monitor.cc in Sources */ = {isa = PBXBuildFile; fileRef = B67BF448216EB43000CA9097 /* create_noop_connectivity_monitor.cc */; }; + AAC15E7CCAE79619B2ABB972 /* XCTestCase+Await.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */; }; + AAF2F02E77A80C9CDE2C0C7A /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CF82019382300D97691 /* target_id_generator_test.cc */; }; AB380CFE201A2F4500D97691 /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; }; @@ -194,10 +487,20 @@ ABC1D7E42024AFDE00BA84F0 /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; }; ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; }; ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; + AC6C1E57B18730428CB15E03 /* executor_libdispatch_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.mm */; }; + ACC9369843F5ED3BD2284078 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; + AD86162AC78673BA969F3467 /* FSTQueryCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0892021552A00B64F25 /* FSTQueryCacheTests.mm */; }; + AEBF3F80ACC01AA8A27091CD /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; + B03F286F3AEC3781C386C646 /* FIRNumericTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D5B25E7E7D6873CBA4571841 /* FIRNumericTransformTests.mm */; }; + B192F30DECA8C28007F9B1D0 /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; }; + B220E091D8F4E6DE1EA44F57 /* executor_libdispatch_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.mm */; }; + B49311BDE5EB6DF811E03C1B /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D9342023966E000A432D /* credentials_provider_test.cc */; }; + B513F723728E923DFF34F60F /* leveldb_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */; }; B60894F72170207200EBC644 /* fake_credentials_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = B60894F62170207100EBC644 /* fake_credentials_provider.cc */; }; B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; }; B65D34A9203C995B0076A5E1 /* FIRTimestampTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */; }; B66D8996213609EE0086DA0C /* stream_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B66D8995213609EE0086DA0C /* stream_test.mm */; }; + B67BB1DA1E247A87B4755C26 /* FSTViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E05E202154B900B64F25 /* FSTViewTests.mm */; }; B67BF449216EB43000CA9097 /* create_noop_connectivity_monitor.cc in Sources */ = {isa = PBXBuildFile; fileRef = B67BF448216EB43000CA9097 /* create_noop_connectivity_monitor.cc */; }; B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; }; @@ -217,21 +520,66 @@ B6FB468E208F9BAB00554BA2 /* executor_libdispatch_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.mm */; }; B6FB468F208F9BAE00554BA2 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; B6FB4690208F9BB300554BA2 /* executor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4688208F9B9100554BA2 /* executor_test.cc */; }; + B6FDE6F91D3F81D045E962A0 /* bits_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D01201BC69F00D97691 /* bits_test.cc */; }; + B8062EBDB8E5B680E46A6DD1 /* geo_point_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB7BAB332012B519001E0872 /* geo_point_test.cc */; }; + B89EF6551734723BDC6AB79C /* FSTDocumentKeyTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B22021555000B64F25 /* FSTDocumentKeyTests.mm */; }; + BA9A65BD6D993B2801A3C768 /* grpc_connection_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D9649021544D4F00EB9CFB /* grpc_connection_test.cc */; }; + BAB43C839445782040657239 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; + BBFCCD960DD2937EE278D7B6 /* FSTQueryCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0892021552A00B64F25 /* FSTQueryCacheTests.mm */; }; + BC0C98A9201E8F98B9A176A9 /* FIRWriteBatchTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06F202154D600B64F25 /* FIRWriteBatchTests.mm */; }; + BC2D0A8EA272A0058F6C2B9E /* FIRFirestoreSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */; }; + BCD9AEA4A890E804922BF72F /* FSTRemoteEventTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C32021557E00B64F25 /* FSTRemoteEventTests.mm */; }; BEE0294A23AB993E5DE0E946 /* leveldb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */; }; + C0AD8DB5A84CAAEE36230899 /* status_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352C20A3B3D7003E0143 /* status_test.cc */; }; + C13502E39B0AEF0FADDDA5F2 /* FSTDocumentSetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B32021555100B64F25 /* FSTDocumentSetTests.mm */; }; C1AA536F90A0A576CA2816EB /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */; }; + C1B859FD314E866619683940 /* FSTImmutableSortedSet+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0821F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m */; }; + C1E35BCE2CFF9B56C28545A2 /* Pods_Firestore_Example_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62E103B28B48A81D682A0DE9 /* Pods_Firestore_Example_tvOS.framework */; }; + C21B3A1CCB3AD42E57EA14FC /* Pods_Firestore_Tests_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 759E964B6A03E6775C992710 /* Pods_Firestore_Tests_macOS.framework */; }; + C39CBADA58F442C8D66C3DA2 /* FIRFieldPathTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */; }; + C4055D868A38221B332CD03D /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; C482E724F4B10968417C3F78 /* Pods_Firestore_FuzzTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */; }; + C5655568EC2A9F6B5E6F9141 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; + C5C01A1FB216DA4BA8BF1A02 /* stream_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B66D8995213609EE0086DA0C /* stream_test.mm */; }; + C5DEDF6148FD41B3000DDD5C /* FSTMemoryRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08C2021552B00B64F25 /* FSTMemoryRemoteDocumentCacheTests.mm */; }; + C5F1E2220E30ED5EAC9ABD9E /* mutation.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */; }; + C7F174164D7C55E35A526009 /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; }; C80B10E79CDD7EF7843C321E /* type_traits_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A0CF41BA5AED6049B0BEB2C /* type_traits_apple_test.mm */; }; C8D3CE2343E53223E6487F2C /* Pods_Firestore_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5918805E993304321A05E82B /* Pods_Firestore_Example_iOS.framework */; }; + C9F96C511F45851D38EC449C /* status.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9920B89AAC00B5BCE7 /* status.pb.cc */; }; + CA18CEF2585A6BC4974DB56D /* FSTQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E061202154B900B64F25 /* FSTQueryTests.mm */; }; + CA69FC4DF0C906183CF5DCE9 /* FSTFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */; }; CA989C0E6020C372A62B7062 /* testutil.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352820A3B3BD003E0143 /* testutil.cc */; }; + CD0AA9E5D83C00CAAE7C2F67 /* FIRTimestampTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */; }; + CD78EEAA1CD36BE691CA3427 /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; + CEDDC6DB782989587D0139B2 /* datastore_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 546854A820A36867004BDBD5 /* datastore_test.mm */; }; + D063F56AC89E074F9AB05DD3 /* FSTRemoteDocumentCacheTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E09C2021552D00B64F25 /* FSTRemoteDocumentCacheTests.mm */; }; + D22B96C19A0F3DE998D4320C /* delayed_constructor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D0A6E9136804A41CEC9D55D4 /* delayed_constructor_test.cc */; }; + D44DA2F61B854E8771E4E446 /* memory_index_manager_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73F1F7392210F3D800E1F692 /* memory_index_manager_test.mm */; }; + D572B4D4DBDD6B9235781646 /* objc_compatibility_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B696858F221770F000271095 /* objc_compatibility_apple_test.mm */; }; + D57F4CB3C92CE3D4DF329B78 /* serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 61F72C5520BC48FD001A68CB /* serializer_test.cc */; }; + D59FAEE934987D4C4B2A67B2 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; D5B252EE3F4037405DB1ECE3 /* FIRNumericTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D5B25E7E7D6873CBA4571841 /* FIRNumericTransformTests.mm */; }; D5B25CBF07F65E885C9D68AB /* perf_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = D5B2593BCB52957D62F1C9D3 /* perf_spec_test.json */; }; + D5E9954FC1C5ABBC7A180B33 /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; + D69B97FF4C065EACEDD91886 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; + D6DE74259F5C0CCA010D6A0D /* grpc_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6BBE42F21262CF400C6A53E /* grpc_stream_test.cc */; }; + D6E0E54CD1640E726900828A /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; }; + D77941FD93DBE862AEF1F623 /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; + D9366A834BFF13246DC3AF9E /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; D94A1862B8FB778225DB54A1 /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; + D98A0B6007E271E32299C79D /* FIRGeoPointTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E048202154AA00B64F25 /* FIRGeoPointTests.mm */; }; + DA4303684707606318E1914D /* target_id_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CF82019382300D97691 /* target_id_generator_test.cc */; }; + DAC43DD1FDFBAB1FE1AD6BE5 /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; }; DAFF0CF921E64AC30062958F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFF0CF821E64AC30062958F /* AppDelegate.m */; }; DAFF0CFB21E64AC40062958F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DAFF0CFA21E64AC40062958F /* Assets.xcassets */; }; DAFF0CFE21E64AC40062958F /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = DAFF0CFC21E64AC40062958F /* MainMenu.xib */; }; DAFF0D0121E64AC40062958F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFF0D0021E64AC40062958F /* main.m */; }; DAFF0D0921E653A00062958F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 54D400D32148BACE001D2BCC /* GoogleService-Info.plist */; }; - DD213F68A6F79E1D4924BD95 /* Pods_macOS_example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E42355285B9EF55ABD785792 /* Pods_macOS_example.framework */; }; + DB7E9C5A59CCCDDB7F0C238A /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; }; + DBDC8E997E909804F1B43E92 /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; }; + DC48407370E87F2233D7AB7E /* statusor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352D20A3B3D7003E0143 /* statusor_test.cc */; }; + DD213F68A6F79E1D4924BD95 /* Pods_Firestore_Example_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E42355285B9EF55ABD785792 /* Pods_Firestore_Example_macOS.framework */; }; DD5976A45071455FF3FE74B8 /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; }; DE03B2D41F2149D600A30B9C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; DE03B2D51F2149D600A30B9C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; @@ -242,10 +590,81 @@ DE2EF0861F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0801F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m */; }; DE2EF0871F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0821F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m */; }; DE2EF0881F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0841F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m */; }; + DEF036EA1ECEECD8E3ECC362 /* FSTImmutableSortedSet+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0821F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m */; }; + E084921EFB7CF8CB1E950D6C /* iterator_adaptors_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */; }; + E0E640226A1439C59BBBA9C1 /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; + E11DDA3DD75705F26245E295 /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; + E2B15548A3B6796CE5A01975 /* FIRListenerRegistrationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06B202154D500B64F25 /* FIRListenerRegistrationTests.mm */; }; + E375FBA0632EFB4D14C4E5A9 /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; + E3C0E5F834A82EEE9F8C4519 /* watch_change_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B68FC0E421F6848700A7055C /* watch_change_test.mm */; }; + E4C0CC7FB88D8F6CB1B972C6 /* leveldb_index_manager_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73F1F7402211FEF300E1F692 /* leveldb_index_manager_test.mm */; }; + E4EEF6AAFCD33303CE9E5408 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; + E500AB82DF2E7F3AFDB1AB3F /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; + E50187548B537DBCDBF7F9F0 /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; }; + E6821243C510797EFFC7BCE2 /* grpc_streaming_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964922154AB8F00EB9CFB /* grpc_streaming_reader_test.cc */; }; + E764F0F389E7119220EB212C /* target_id_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CF82019382300D97691 /* target_id_generator_test.cc */; }; + E7D415B8717701B952C344E5 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; + E82F8EBBC8CC37299A459E73 /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; + E9558682F8A4DD3E7C85C067 /* FSTLevelDBMigrationsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0862021552A00B64F25 /* FSTLevelDBMigrationsTests.mm */; }; + E980E1DCF759D5EF9F6B98F2 /* FSTDocumentTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B62021555100B64F25 /* FSTDocumentTests.mm */; }; + EA38690795FBAA182A9AA63E /* FIRDatabaseTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06C202154D500B64F25 /* FIRDatabaseTests.mm */; }; + EB04FE18E5794FEC187A09E3 /* FSTMemorySpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */; }; EBFC611B1BF195D0EC710AF4 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; }; + EC160876D8A42166440E0B53 /* FIRCursorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E070202154D600B64F25 /* FIRCursorTests.mm */; }; + EC80A217F3D66EB0272B36B0 /* FSTLevelDBSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */; }; + ED420D8F49DA5C41EEF93913 /* FIRSnapshotMetadataTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */; }; + ED4E2AC80CAF2A8FDDAC3DEE /* field_mask_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */; }; + EF3518F84255BAF3EBD317F6 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; + F007A46BE03A01C077EFCBD8 /* FSTFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */; }; + F1661B1C5F3E30535FB65046 /* FSTArraySortedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF07E1F3D0B6E003D0CDC /* FSTArraySortedDictionaryTests.m */; }; + F19B749671F2552E964422F7 /* FIRListenerRegistrationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06B202154D500B64F25 /* FIRListenerRegistrationTests.mm */; }; + F3261CBFC169DB375A0D9492 /* FSTMockDatastore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02D20213FFC00B64F25 /* FSTMockDatastore.mm */; }; + F3F09BC931A717CEFF4E14B9 /* FIRFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */; }; + F46394FAA186BC6D19213B59 /* FSTLevelDBLocalStoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E08F2021552B00B64F25 /* FSTLevelDBLocalStoreTests.mm */; }; + F481368DB694B3B4D0C8E4A2 /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B9C261C26C5D311E1E3C0CB9 /* query_test.cc */; }; + F4F00BF4E87D7F0F0F8831DB /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; }; + F58A4EE0A1A77F61EF41E5ED /* FSTSerializerBetaTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C12021557E00B64F25 /* FSTSerializerBetaTests.mm */; }; + F72DF72447EA7AB9D100816A /* FSTHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03A2021401F00B64F25 /* FSTHelpers.mm */; }; + F7718C43D3A8FCCDB4BB0071 /* geo_point_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB7BAB332012B519001E0872 /* geo_point_test.cc */; }; + F8D3EF0C9044BB3F3E81C6CA /* FSTDocumentSetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B32021555100B64F25 /* FSTDocumentSetTests.mm */; }; + F9DC01FCBE76CD4F0453A67C /* strerror_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */; }; + FA63B7521A07F1EB2F999859 /* FSTLevelDBMigrationsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0862021552A00B64F25 /* FSTLevelDBMigrationsTests.mm */; }; + FA7837C5CDFB273DE447E447 /* FIRServerTimestampTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */; }; + FD8EA96A604E837092ACA51D /* ordered_code_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D03201BC6E400D97691 /* ordered_code_test.cc */; }; + FEF55ECFB0CA317B351179AB /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; }; + FF3405218188DFCE586FB26B /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; }; + FF4FA5757D13A2B7CEE40F04 /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D821C2DDC800EFB9CC /* document.pb.cc */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 544AB1972248072200F851E6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6003F582195388D10070C39A /* Project object */; + proxyType = 1; + remoteGlobalIDString = DAFF0CF421E64AC30062958F; + remoteInfo = Firestore_Example_macOS; + }; + 54AA33AB224BFE0A006CE580 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6003F582195388D10070C39A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 54AA338E224BF935006CE580; + remoteInfo = Firestore_Example_tvOS; + }; + 54AA33B9224C0035006CE580 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6003F582195388D10070C39A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 54AA338E224BF935006CE580; + remoteInfo = Firestore_Example_tvOS; + }; + 54B8E4AF224BDC4100930F18 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6003F582195388D10070C39A /* Project object */; + proxyType = 1; + remoteGlobalIDString = DAFF0CF421E64AC30062958F; + remoteInfo = Firestore_Example_macOS; + }; 54C9EDF62040E16300A969CD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 6003F582195388D10070C39A /* Project object */; @@ -312,15 +731,22 @@ 132E32997D781B896672D30A /* reference_set_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = reference_set_test.cc; sourceTree = ""; }; 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBTransactionTests.mm; sourceTree = ""; }; 132E3BB3D5C42282B4ACFB20 /* FSTLevelDBBenchmarkTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBBenchmarkTests.mm; sourceTree = ""; }; + 2220F583583EFC28DE792ABE /* Pods_Firestore_IntegrationTests_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2A0CF41BA5AED6049B0BEB2C /* type_traits_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = type_traits_apple_test.mm; sourceTree = ""; }; 2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2E48431B0EDA400BEA91D4AB /* Pods-Firestore_Tests_tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_tvOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS.debug.xcconfig"; sourceTree = ""; }; + 2F901F31BC62444A476B779F /* Pods-Firestore_IntegrationTests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests_macOS/Pods-Firestore_IntegrationTests_macOS.debug.xcconfig"; sourceTree = ""; }; 332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_util_test.cc; sourceTree = ""; }; 353EEE078EF3F39A9B7279F6 /* nanopb_string_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = nanopb_string_test.cc; path = nanopb/nanopb_string_test.cc; sourceTree = ""; }; 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = strerror_test.cc; sourceTree = ""; }; + 36D235D9F1240D5195CDB670 /* Pods-Firestore_IntegrationTests_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests_tvOS/Pods-Firestore_IntegrationTests_tvOS.release.xcconfig"; sourceTree = ""; }; + 397FB002E298B780F1E223E2 /* Pods-Firestore_Tests_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_macOS/Pods-Firestore_Tests_macOS.release.xcconfig"; sourceTree = ""; }; + 39B832380209CC5BAF93BC52 /* Pods_Firestore_IntegrationTests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = remote_store_spec_test.json; sourceTree = ""; }; 3C81DE3772628FE297055662 /* Pods-Firestore_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.debug.xcconfig"; sourceTree = ""; }; 3F0992A4B83C60841C52E960 /* Pods-Firestore_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.release.xcconfig"; sourceTree = ""; }; 403DBF6EFB541DFD01582AA3 /* path_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = path_test.cc; sourceTree = ""; }; + 4425A513895DEC60325A139E /* xcgmock_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = xcgmock_test.mm; sourceTree = ""; }; 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = hard_assert_test.cc; sourceTree = ""; }; 54131E9620ADE678001DF3FF /* string_format_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_format_test.cc; sourceTree = ""; }; 544129D021C2DDC800EFB9CC /* query.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = query.pb.h; sourceTree = ""; }; @@ -333,6 +759,7 @@ 544129D721C2DDC800EFB9CC /* document.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = document.pb.h; sourceTree = ""; }; 544129D821C2DDC800EFB9CC /* document.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = document.pb.cc; sourceTree = ""; }; 544129D921C2DDC800EFB9CC /* write.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = write.pb.cc; sourceTree = ""; }; + 544AB1922248072200F851E6 /* Firestore_Tests_macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_Tests_macOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54511E8D209805F8005BD28F /* hashing_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hashing_test.cc; sourceTree = ""; }; 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFirestoreTests.mm; sourceTree = ""; }; 5467FB06203E6A44009C9584 /* app_testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = app_testing.h; sourceTree = ""; }; @@ -407,6 +834,7 @@ 5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFieldValueTests.mm; sourceTree = ""; }; 5492E0C12021557E00B64F25 /* FSTSerializerBetaTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTSerializerBetaTests.mm; sourceTree = ""; }; 5492E0C32021557E00B64F25 /* FSTRemoteEventTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTRemoteEventTests.mm; sourceTree = ""; }; + 5493A423225F9990006DE7BA /* status_apple_test.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = status_apple_test.mm; sourceTree = ""; }; 5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodableGeoPointTests.swift; sourceTree = ""; }; 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_key_test.cc; sourceTree = ""; }; 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sorted_set_test.cc; sourceTree = ""; }; @@ -423,6 +851,18 @@ 54A0352C20A3B3D7003E0143 /* status_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = status_test.cc; sourceTree = ""; }; 54A0352D20A3B3D7003E0143 /* statusor_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = statusor_test.cc; sourceTree = ""; }; 54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = iterator_adaptors_test.cc; sourceTree = ""; }; + 54AA338F224BF935006CE580 /* Firestore_Example_tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Firestore_Example_tvOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 54AA3391224BF935006CE580 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 54AA3392224BF935006CE580 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 54AA3394224BF935006CE580 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 54AA3395224BF935006CE580 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 54AA3398224BF935006CE580 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 54AA339A224BF936006CE580 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 54AA339C224BF936006CE580 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 54AA339D224BF936006CE580 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 54AA33A6224BFE09006CE580 /* Firestore_Tests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_Tests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 54AA33B4224C0035006CE580 /* Firestore_IntegrationTests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_IntegrationTests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 54B8E4AA224BDC4100930F18 /* Firestore_IntegrationTests_macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_IntegrationTests_macOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54C2294E1FECABAE007D065B /* log_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = log_test.cc; sourceTree = ""; }; 54C9EDF12040E16300A969CD /* Firestore_SwiftTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_SwiftTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54C9EDF52040E16300A969CD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -480,7 +920,9 @@ 618BBE9920B89AAC00B5BCE7 /* status.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = status.pb.cc; sourceTree = ""; }; 618BBE9A20B89AAC00B5BCE7 /* status.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = status.pb.h; sourceTree = ""; }; 61F72C5520BC48FD001A68CB /* serializer_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = serializer_test.cc; sourceTree = ""; }; + 62E103B28B48A81D682A0DE9 /* Pods_Firestore_Example_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = ""; }; + 6AE927CDFC7A72BF825BE4CB /* Pods-Firestore_Tests_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS.release.xcconfig"; sourceTree = ""; }; 6E8302DE210222ED003E1EA3 /* FSTFuzzTestFieldPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTFuzzTestFieldPath.h; sourceTree = ""; }; 6E8302DF21022309003E1EA3 /* FSTFuzzTestFieldPath.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestFieldPath.mm; sourceTree = ""; }; 6EA39FDD20FE820E008D461F /* FSTFuzzTestSerializer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestSerializer.mm; sourceTree = ""; }; @@ -490,15 +932,23 @@ 6EDD3B5C20BF247500C33877 /* Firestore_FuzzTests_iOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Firestore_FuzzTests_iOS-Info.plist"; sourceTree = ""; }; 6EDD3B5E20BF24D000C33877 /* FSTFuzzTestsPrincipal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestsPrincipal.mm; sourceTree = ""; }; 71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 731541602214AFFA0037F4DC /* query_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = query_spec_test.json; sourceTree = ""; }; 73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRArrayTransformTests.mm; sourceTree = ""; }; + 73F1F7392210F3D800E1F692 /* memory_index_manager_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = memory_index_manager_test.mm; sourceTree = ""; }; + 73F1F73A2210F3D800E1F692 /* index_manager_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = index_manager_test.h; sourceTree = ""; }; + 73F1F73B2210F3D800E1F692 /* index_manager_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = index_manager_test.mm; sourceTree = ""; }; + 73F1F7402211FEF300E1F692 /* leveldb_index_manager_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = leveldb_index_manager_test.mm; sourceTree = ""; }; + 74AC2ADBF1BAD9A8EF30CF41 /* Pods-Firestore_IntegrationTests_tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests_tvOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests_tvOS/Pods-Firestore_IntegrationTests_tvOS.debug.xcconfig"; sourceTree = ""; }; + 759E964B6A03E6775C992710 /* Pods_Firestore_Tests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 79507DF8378D3C42F5B36268 /* string_win_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = string_win_test.cc; sourceTree = ""; }; 84434E57CA72951015FC71BC /* Pods-Firestore_FuzzTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_FuzzTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS.debug.xcconfig"; sourceTree = ""; }; 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 8E002F4AD5D9B6197C940847 /* Firestore.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Firestore.podspec; path = ../Firestore.podspec; sourceTree = ""; }; 97C492D2524E92927C11F425 /* Pods-Firestore_FuzzTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_FuzzTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS.release.xcconfig"; sourceTree = ""; }; - 98366480BD1FD44A1FEDD982 /* Pods-macOS_example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macOS_example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-macOS_example/Pods-macOS_example.debug.xcconfig"; sourceTree = ""; }; + 98366480BD1FD44A1FEDD982 /* Pods-Firestore_Example_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_macOS/Pods-Firestore_Example_macOS.debug.xcconfig"; sourceTree = ""; }; 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = string_format_apple_test.mm; sourceTree = ""; }; A5FA86650A18F3B7A8162287 /* Pods-Firestore_Benchmarks_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Benchmarks_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS.release.xcconfig"; sourceTree = ""; }; + A70E82DD627B162BEF92B8ED /* Pods-Firestore_Example_tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_tvOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_tvOS/Pods-Firestore_Example_tvOS.debug.xcconfig"; sourceTree = ""; }; AB356EF6200EA5EB0089B766 /* field_value_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = field_value_test.cc; sourceTree = ""; }; AB380CF82019382300D97691 /* target_id_generator_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = target_id_generator_test.cc; sourceTree = ""; }; AB380CFC201A2EE200D97691 /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_util_test.cc; sourceTree = ""; }; @@ -548,20 +998,25 @@ B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = executor_libdispatch_test.mm; sourceTree = ""; }; B6FB468A208F9B9100554BA2 /* executor_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = executor_test.h; sourceTree = ""; }; B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_FuzzTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B953604968FBF5483BD20F5A /* Pods-Firestore_IntegrationTests_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests_macOS/Pods-Firestore_IntegrationTests_macOS.release.xcconfig"; sourceTree = ""; }; B9C261C26C5D311E1E3C0CB9 /* query_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = query_test.cc; sourceTree = ""; }; + BA6E5B9D53CCF301F58A62D7 /* xcgmock.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = xcgmock.h; sourceTree = ""; }; BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BD01F0E43E4E2A07B8B05099 /* Pods-Firestore_Tests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_macOS/Pods-Firestore_Tests_macOS.debug.xcconfig"; sourceTree = ""; }; C8522DE226C467C54E6788D8 /* mutation_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = mutation_test.cc; sourceTree = ""; }; + D0A6E9136804A41CEC9D55D4 /* delayed_constructor_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = delayed_constructor_test.cc; sourceTree = ""; }; D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; D5B2593BCB52957D62F1C9D3 /* perf_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = perf_spec_test.json; sourceTree = ""; }; D5B25E7E7D6873CBA4571841 /* FIRNumericTransformTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRNumericTransformTests.mm; sourceTree = ""; }; - DAFF0CF521E64AC30062958F /* macOS_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = macOS_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D7DF4A6F740086A2D8C0E28E /* Pods_Firestore_Tests_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DAFF0CF521E64AC30062958F /* Firestore_Example_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Firestore_Example_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; DAFF0CF721E64AC30062958F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; DAFF0CF821E64AC30062958F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; DAFF0CFA21E64AC40062958F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DAFF0CFD21E64AC40062958F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; DAFF0CFF21E64AC40062958F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DAFF0D0021E64AC40062958F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - DAFF0D0221E64AC40062958F /* macOS_example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS_example.entitlements; sourceTree = ""; }; + DAFF0D0221E64AC40062958F /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; DAFF0D0721E653460062958F /* roots.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = roots.pem; path = ../../../../etc/roots.pem; sourceTree = ""; }; DE03B2E91F2149D600A30B9C /* Firestore_IntegrationTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_IntegrationTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DE03B3621F215E1600A30B9C /* CAcert.pem */ = {isa = PBXFileReference; lastKnownFileType = text; path = CAcert.pem; sourceTree = ""; }; @@ -578,8 +1033,8 @@ DE51B1981F0D48AC0013853F /* FSTSpecTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTSpecTests.h; sourceTree = ""; }; DE51B19A1F0D48AC0013853F /* FSTSyncEngineTestDriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTSyncEngineTestDriver.h; sourceTree = ""; }; DE51B1A71F0D48AC0013853F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - DF148C0D5EEC4A2CD9FA484C /* Pods-macOS_example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macOS_example.release.xcconfig"; path = "Pods/Target Support Files/Pods-macOS_example/Pods-macOS_example.release.xcconfig"; sourceTree = ""; }; - E42355285B9EF55ABD785792 /* Pods_macOS_example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_macOS_example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DF148C0D5EEC4A2CD9FA484C /* Pods-Firestore_Example_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_macOS/Pods-Firestore_Example_macOS.release.xcconfig"; sourceTree = ""; }; + E42355285B9EF55ABD785792 /* Pods_Firestore_Example_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.debug.xcconfig"; sourceTree = ""; }; ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ED4B3E3EA0EBF3ED19A07060 /* grpc_stream_tester.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = grpc_stream_tester.h; sourceTree = ""; }; @@ -588,9 +1043,50 @@ F694C3CE4B77B3C0FA4BBA53 /* Pods_Firestore_Benchmarks_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Benchmarks_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F8043813A5D16963EC02B182 /* local_serializer_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = local_serializer_test.cc; sourceTree = ""; }; FA2E9952BA2B299C1156C43C /* Pods-Firestore_Benchmarks_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; sourceTree = ""; }; + FC738525340E594EBFAB121E /* Pods-Firestore_Example_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_tvOS/Pods-Firestore_Example_tvOS.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 544AB18F2248072200F851E6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C21B3A1CCB3AD42E57EA14FC /* Pods_Firestore_Tests_macOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54AA338C224BF935006CE580 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C1E35BCE2CFF9B56C28545A2 /* Pods_Firestore_Example_tvOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54AA33A3224BFE09006CE580 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4CC78CA0E9E03F5DCF13FEBD /* Pods_Firestore_Tests_tvOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54AA33B1224C0035006CE580 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 20A26E9D0336F7F32A098D05 /* Pods_Firestore_IntegrationTests_tvOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54B8E4A7224BDC4100930F18 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 358DBA8B2560C65D9EB23C35 /* Pods_Firestore_IntegrationTests_macOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 54C9EDEE2040E16300A969CD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -644,7 +1140,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DD213F68A6F79E1D4924BD95 /* Pods_macOS_example.framework in Frameworks */, + DD213F68A6F79E1D4924BD95 /* Pods_Firestore_Example_macOS.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -666,7 +1162,8 @@ isa = PBXGroup; children = ( 6003F593195388D20070C39A /* iOS */, - DAFF0CF621E64AC30062958F /* macOS_example */, + DAFF0CF621E64AC30062958F /* macOS */, + 54AA3390224BF935006CE580 /* tvOS */, 54D400D32148BACE001D2BCC /* GoogleService-Info.plist */, ); path = App; @@ -704,6 +1201,8 @@ 5467FB07203E6A44009C9584 /* app_testing.mm */, 54A0352820A3B3BD003E0143 /* testutil.cc */, 54A0352920A3B3BD003E0143 /* testutil.h */, + BA6E5B9D53CCF301F58A62D7 /* xcgmock.h */, + 4425A513895DEC60325A139E /* xcgmock_test.mm */, ); path = testutil; sourceTree = ""; @@ -737,6 +1236,7 @@ 548DB928200D59F600E00ABC /* comparison_test.cc */, B67BF448216EB43000CA9097 /* create_noop_connectivity_monitor.cc */, B67BF447216EB42F00CA9097 /* create_noop_connectivity_monitor.h */, + D0A6E9136804A41CEC9D55D4 /* delayed_constructor_test.cc */, B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.mm */, B6FB4687208F9B9100554BA2 /* executor_std_test.cc */, B6FB4688208F9B9100554BA2 /* executor_test.cc */, @@ -755,6 +1255,7 @@ AB380D03201BC6E400D97691 /* ordered_code_test.cc */, 403DBF6EFB541DFD01582AA3 /* path_test.cc */, 54740A531FC913E500713A1A /* secure_random_test.cc */, + 5493A423225F9990006DE7BA /* status_apple_test.mm */, 54A0352C20A3B3D7003E0143 /* status_test.cc */, 54A0352B20A3B3D7003E0143 /* status_test_util.h */, 54A0352D20A3B3D7003E0143 /* statusor_test.cc */, @@ -802,14 +1303,33 @@ 54995F70205B6E1A004EFFA0 /* local */ = { isa = PBXGroup; children = ( + 73F1F73A2210F3D800E1F692 /* index_manager_test.h */, + 73F1F73B2210F3D800E1F692 /* index_manager_test.mm */, + 73F1F7402211FEF300E1F692 /* leveldb_index_manager_test.mm */, 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */, 332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */, F8043813A5D16963EC02B182 /* local_serializer_test.cc */, + 73F1F7392210F3D800E1F692 /* memory_index_manager_test.mm */, 132E32997D781B896672D30A /* reference_set_test.cc */, ); path = local; sourceTree = ""; }; + 54AA3390224BF935006CE580 /* tvOS */ = { + isa = PBXGroup; + children = ( + 54AA3391224BF935006CE580 /* AppDelegate.h */, + 54AA3392224BF935006CE580 /* AppDelegate.m */, + 54AA339A224BF936006CE580 /* Assets.xcassets */, + 54AA339C224BF936006CE580 /* Info.plist */, + 54AA3397224BF935006CE580 /* Main.storyboard */, + 54AA3394224BF935006CE580 /* ViewController.h */, + 54AA3395224BF935006CE580 /* ViewController.m */, + 54AA339D224BF936006CE580 /* main.m */, + ); + path = tvOS; + sourceTree = ""; + }; 54C9EDF22040E16300A969CD /* SwiftTests */ = { isa = PBXGroup; children = ( @@ -872,11 +1392,16 @@ children = ( 5CAE131920FFFED600BE9A4A /* Firestore_Benchmarks_iOS.xctest */, 6003F58A195388D20070C39A /* Firestore_Example_iOS.app */, + DAFF0CF521E64AC30062958F /* Firestore_Example_macOS.app */, + 54AA338F224BF935006CE580 /* Firestore_Example_tvOS.app */, 6EDD3B5B20BF247500C33877 /* Firestore_FuzzTests_iOS.xctest */, DE03B2E91F2149D600A30B9C /* Firestore_IntegrationTests_iOS.xctest */, + 54B8E4AA224BDC4100930F18 /* Firestore_IntegrationTests_macOS.xctest */, + 54AA33B4224C0035006CE580 /* Firestore_IntegrationTests_tvOS.xctest */, 54C9EDF12040E16300A969CD /* Firestore_SwiftTests_iOS.xctest */, 6003F5AE195388D20070C39A /* Firestore_Tests_iOS.xctest */, - DAFF0CF521E64AC30062958F /* macOS_example.app */, + 544AB1922248072200F851E6 /* Firestore_Tests_macOS.xctest */, + 54AA33A6224BFE09006CE580 /* Firestore_Tests_tvOS.xctest */, ); name = Products; sourceTree = ""; @@ -889,10 +1414,15 @@ F694C3CE4B77B3C0FA4BBA53 /* Pods_Firestore_Benchmarks_iOS.framework */, 5918805E993304321A05E82B /* Pods_Firestore_Example_iOS.framework */, BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */, + E42355285B9EF55ABD785792 /* Pods_Firestore_Example_macOS.framework */, + 62E103B28B48A81D682A0DE9 /* Pods_Firestore_Example_tvOS.framework */, B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */, ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */, + 39B832380209CC5BAF93BC52 /* Pods_Firestore_IntegrationTests_macOS.framework */, + 2220F583583EFC28DE792ABE /* Pods_Firestore_IntegrationTests_tvOS.framework */, 2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */, - E42355285B9EF55ABD785792 /* Pods_macOS_example.framework */, + 759E964B6A03E6775C992710 /* Pods_Firestore_Tests_macOS.framework */, + D7DF4A6F740086A2D8C0E28E /* Pods_Firestore_Tests_tvOS.framework */, 6003F591195388D20070C39A /* UIKit.framework */, 6003F5AF195388D20070C39A /* XCTest.framework */, ); @@ -1062,14 +1592,24 @@ 11984BA0A99D7A7ABA5B0D90 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig */, 3C81DE3772628FE297055662 /* Pods-Firestore_Example_iOS.debug.xcconfig */, 3F0992A4B83C60841C52E960 /* Pods-Firestore_Example_iOS.release.xcconfig */, + 98366480BD1FD44A1FEDD982 /* Pods-Firestore_Example_macOS.debug.xcconfig */, + DF148C0D5EEC4A2CD9FA484C /* Pods-Firestore_Example_macOS.release.xcconfig */, + A70E82DD627B162BEF92B8ED /* Pods-Firestore_Example_tvOS.debug.xcconfig */, + FC738525340E594EBFAB121E /* Pods-Firestore_Example_tvOS.release.xcconfig */, 84434E57CA72951015FC71BC /* Pods-Firestore_FuzzTests_iOS.debug.xcconfig */, 97C492D2524E92927C11F425 /* Pods-Firestore_FuzzTests_iOS.release.xcconfig */, 1277F98C20D2DF0867496976 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */, F354C0FE92645B56A6C6FD44 /* Pods-Firestore_IntegrationTests_iOS.release.xcconfig */, + 2F901F31BC62444A476B779F /* Pods-Firestore_IntegrationTests_macOS.debug.xcconfig */, + B953604968FBF5483BD20F5A /* Pods-Firestore_IntegrationTests_macOS.release.xcconfig */, + 74AC2ADBF1BAD9A8EF30CF41 /* Pods-Firestore_IntegrationTests_tvOS.debug.xcconfig */, + 36D235D9F1240D5195CDB670 /* Pods-Firestore_IntegrationTests_tvOS.release.xcconfig */, E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */, B3F5B3AAE791A5911B9EAA82 /* Pods-Firestore_Tests_iOS.release.xcconfig */, - 98366480BD1FD44A1FEDD982 /* Pods-macOS_example.debug.xcconfig */, - DF148C0D5EEC4A2CD9FA484C /* Pods-macOS_example.release.xcconfig */, + BD01F0E43E4E2A07B8B05099 /* Pods-Firestore_Tests_macOS.debug.xcconfig */, + 397FB002E298B780F1E223E2 /* Pods-Firestore_Tests_macOS.release.xcconfig */, + 2E48431B0EDA400BEA91D4AB /* Pods-Firestore_Tests_tvOS.debug.xcconfig */, + 6AE927CDFC7A72BF825BE4CB /* Pods-Firestore_Tests_tvOS.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -1114,7 +1654,7 @@ path = auth; sourceTree = ""; }; - DAFF0CF621E64AC30062958F /* macOS_example */ = { + DAFF0CF621E64AC30062958F /* macOS */ = { isa = PBXGroup; children = ( DAFF0D0621E652FD0062958F /* resources */, @@ -1123,10 +1663,10 @@ DAFF0CFA21E64AC40062958F /* Assets.xcassets */, DAFF0CFF21E64AC40062958F /* Info.plist */, DAFF0CFC21E64AC40062958F /* MainMenu.xib */, - DAFF0D0221E64AC40062958F /* macOS_example.entitlements */, + DAFF0D0221E64AC40062958F /* macOS.entitlements */, DAFF0D0021E64AC40062958F /* main.m */, ); - path = macOS_example; + path = macOS; sourceTree = ""; }; DAFF0D0621E652FD0062958F /* resources */ = { @@ -1262,6 +1802,7 @@ 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */, D5B2593BCB52957D62F1C9D3 /* perf_spec_test.json */, 54DA12A31F315EE100DD57A1 /* persistence_spec_test.json */, + 731541602214AFFA0037F4DC /* query_spec_test.json */, 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */, 54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */, 54DA12A51F315EE100DD57A1 /* write_spec_test.json */, @@ -1325,6 +1866,105 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 544AB1912248072200F851E6 /* Firestore_Tests_macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 544AB19B2248072200F851E6 /* Build configuration list for PBXNativeTarget "Firestore_Tests_macOS" */; + buildPhases = ( + 30108B32BF2B385AECDB7FB2 /* [CP] Check Pods Manifest.lock */, + 544AB18E2248072200F851E6 /* Sources */, + 544AB18F2248072200F851E6 /* Frameworks */, + 544AB1902248072200F851E6 /* Resources */, + 7E4A6E169B172874E17A3ECA /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 544AB1982248072200F851E6 /* PBXTargetDependency */, + ); + name = Firestore_Tests_macOS; + productName = Firestore_Tests_macOS; + productReference = 544AB1922248072200F851E6 /* Firestore_Tests_macOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 54AA338E224BF935006CE580 /* Firestore_Example_tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 54AA33A1224BF936006CE580 /* Build configuration list for PBXNativeTarget "Firestore_Example_tvOS" */; + buildPhases = ( + 8748E45246D96175497949A5 /* [CP] Check Pods Manifest.lock */, + 54AA338B224BF935006CE580 /* Sources */, + 54AA338C224BF935006CE580 /* Frameworks */, + 54AA338D224BF935006CE580 /* Resources */, + 264B3405701AA9DC9F07658B /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Firestore_Example_tvOS; + productName = Firestore_Example_tvOS; + productReference = 54AA338F224BF935006CE580 /* Firestore_Example_tvOS.app */; + productType = "com.apple.product-type.application"; + }; + 54AA33A5224BFE09006CE580 /* Firestore_Tests_tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 54AA33AF224BFE0A006CE580 /* Build configuration list for PBXNativeTarget "Firestore_Tests_tvOS" */; + buildPhases = ( + A4274FBF1C966A0513CBD0F6 /* [CP] Check Pods Manifest.lock */, + 54AA33A2224BFE09006CE580 /* Sources */, + 54AA33A3224BFE09006CE580 /* Frameworks */, + 54AA33A4224BFE09006CE580 /* Resources */, + 1B1BCDC6BB656D6B79D246DD /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 54AA33AC224BFE0A006CE580 /* PBXTargetDependency */, + ); + name = Firestore_Tests_tvOS; + productName = Firestore_Tests_tvOS; + productReference = 54AA33A6224BFE09006CE580 /* Firestore_Tests_tvOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 54AA33B3224C0035006CE580 /* Firestore_IntegrationTests_tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 54AA33BB224C0035006CE580 /* Build configuration list for PBXNativeTarget "Firestore_IntegrationTests_tvOS" */; + buildPhases = ( + 6800EBA4F597F7115445FCB5 /* [CP] Check Pods Manifest.lock */, + 54AA33B0224C0035006CE580 /* Sources */, + 54AA33B1224C0035006CE580 /* Frameworks */, + 54AA33B2224C0035006CE580 /* Resources */, + 76368D74F155BC9491DC124E /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 54AA33BA224C0035006CE580 /* PBXTargetDependency */, + ); + name = Firestore_IntegrationTests_tvOS; + productName = Firestore_IntegrationTests_tvOS; + productReference = 54AA33B4224C0035006CE580 /* Firestore_IntegrationTests_tvOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 54B8E4A9224BDC4100930F18 /* Firestore_IntegrationTests_macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 54B8E4B3224BDC4100930F18 /* Build configuration list for PBXNativeTarget "Firestore_IntegrationTests_macOS" */; + buildPhases = ( + 54D4C01B433CAC3C4EEDB1F9 /* [CP] Check Pods Manifest.lock */, + 54B8E4A6224BDC4100930F18 /* Sources */, + 54B8E4A7224BDC4100930F18 /* Frameworks */, + 54B8E4A8224BDC4100930F18 /* Resources */, + C164AD918C826AF88B418DA5 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 54B8E4B0224BDC4100930F18 /* PBXTargetDependency */, + ); + name = Firestore_IntegrationTests_macOS; + productName = Firestore_IntegrationTests_macOS; + productReference = 54B8E4AA224BDC4100930F18 /* Firestore_IntegrationTests_macOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 54C9EDF02040E16300A969CD /* Firestore_SwiftTests_iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 54C9EDFA2040E16300A969CD /* Build configuration list for PBXNativeTarget "Firestore_SwiftTests_iOS" */; @@ -1425,9 +2065,9 @@ productReference = 6EDD3B5B20BF247500C33877 /* Firestore_FuzzTests_iOS.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - DAFF0CF421E64AC30062958F /* macOS_example */ = { + DAFF0CF421E64AC30062958F /* Firestore_Example_macOS */ = { isa = PBXNativeTarget; - buildConfigurationList = DAFF0D0521E64AC40062958F /* Build configuration list for PBXNativeTarget "macOS_example" */; + buildConfigurationList = DAFF0D0521E64AC40062958F /* Build configuration list for PBXNativeTarget "Firestore_Example_macOS" */; buildPhases = ( 7C2467DCD3E3E16FB0A737DE /* [CP] Check Pods Manifest.lock */, DAFF0CF121E64AC30062958F /* Sources */, @@ -1439,9 +2079,9 @@ ); dependencies = ( ); - name = macOS_example; - productName = macOS_example; - productReference = DAFF0CF521E64AC30062958F /* macOS_example.app */; + name = Firestore_Example_macOS; + productName = Firestore_Example_macOS; + productReference = DAFF0CF521E64AC30062958F /* Firestore_Example_macOS.app */; productType = "com.apple.product-type.application"; }; DE03B2941F2149D600A30B9C /* Firestore_IntegrationTests_iOS */ = { @@ -1472,9 +2112,33 @@ attributes = { CLASSPREFIX = FIR; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = Google; TargetAttributes = { + 544AB1912248072200F851E6 = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + TestTargetID = DAFF0CF421E64AC30062958F; + }; + 54AA338E224BF935006CE580 = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + }; + 54AA33A5224BFE09006CE580 = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + TestTargetID = 54AA338E224BF935006CE580; + }; + 54AA33B3224C0035006CE580 = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + TestTargetID = 54AA338E224BF935006CE580; + }; + 54B8E4A9224BDC4100930F18 = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + TestTargetID = DAFF0CF421E64AC30062958F; + }; 54C9EDF02040E16300A969CD = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Automatic; @@ -1482,33 +2146,30 @@ }; 5CAE131820FFFED600BE9A4A = { CreatedOnToolsVersion = 9.3.1; - DevelopmentTeam = EQHXZ8M8AV; ProvisioningStyle = Automatic; TestTargetID = 6003F589195388D20070C39A; }; 6003F5AD195388D20070C39A = { - DevelopmentTeam = EQHXZ8M8AV; + CreatedOnToolsVersion = 9.0; TestTargetID = 6003F589195388D20070C39A; }; 6EDD3AD120BF247500C33877 = { - DevelopmentTeam = EQHXZ8M8AV; + CreatedOnToolsVersion = 9.0; }; DAFF0CF421E64AC30062958F = { CreatedOnToolsVersion = 10.0; - DevelopmentTeam = 63PT2H4G8K; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { - enabled = 1; + enabled = 0; }; }; }; DE03B2941F2149D600A30B9C = { - DevelopmentTeam = EQHXZ8M8AV; + CreatedOnToolsVersion = 9.0; }; DE29E7F51F2174B000909613 = { CreatedOnToolsVersion = 9.0; - DevelopmentTeam = EQHXZ8M8AV; }; }; }; @@ -1532,12 +2193,81 @@ DE29E7F51F2174B000909613 /* AllTests_iOS */, 6EDD3AD120BF247500C33877 /* Firestore_FuzzTests_iOS */, 5CAE131820FFFED600BE9A4A /* Firestore_Benchmarks_iOS */, - DAFF0CF421E64AC30062958F /* macOS_example */, + DAFF0CF421E64AC30062958F /* Firestore_Example_macOS */, + 544AB1912248072200F851E6 /* Firestore_Tests_macOS */, + 54B8E4A9224BDC4100930F18 /* Firestore_IntegrationTests_macOS */, + 54AA338E224BF935006CE580 /* Firestore_Example_tvOS */, + 54AA33A5224BFE09006CE580 /* Firestore_Tests_tvOS */, + 54AA33B3224C0035006CE580 /* Firestore_IntegrationTests_tvOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 544AB1902248072200F851E6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 546877D52248206A005E3DE0 /* collection_spec_test.json in Resources */, + 546877D62248206A005E3DE0 /* existence_filter_spec_test.json in Resources */, + 546877D72248206A005E3DE0 /* limbo_spec_test.json in Resources */, + 546877D82248206A005E3DE0 /* limit_spec_test.json in Resources */, + 546877D92248206A005E3DE0 /* listen_spec_test.json in Resources */, + 546877DA2248206A005E3DE0 /* offline_spec_test.json in Resources */, + 546877DB2248206A005E3DE0 /* orderby_spec_test.json in Resources */, + 546877DC2248206A005E3DE0 /* perf_spec_test.json in Resources */, + 546877DD2248206A005E3DE0 /* persistence_spec_test.json in Resources */, + 546877DE2248206A005E3DE0 /* query_spec_test.json in Resources */, + 546877DF2248206A005E3DE0 /* remote_store_spec_test.json in Resources */, + 546877E02248206A005E3DE0 /* resume_token_spec_test.json in Resources */, + 546877E12248206A005E3DE0 /* write_spec_test.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54AA338D224BF935006CE580 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 54AA339B224BF936006CE580 /* Assets.xcassets in Resources */, + 54ACB6D6224C125B00172E69 /* GoogleService-Info.plist in Resources */, + 54AA3399224BF935006CE580 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54AA33A4224BFE09006CE580 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 54ACB6C9224C11F400172E69 /* collection_spec_test.json in Resources */, + 54ACB6CA224C11F400172E69 /* existence_filter_spec_test.json in Resources */, + 54ACB6CB224C11F400172E69 /* limbo_spec_test.json in Resources */, + 54ACB6CC224C11F400172E69 /* limit_spec_test.json in Resources */, + 54ACB6CD224C11F400172E69 /* listen_spec_test.json in Resources */, + 54ACB6CE224C11F400172E69 /* offline_spec_test.json in Resources */, + 54ACB6CF224C11F400172E69 /* orderby_spec_test.json in Resources */, + 54ACB6D0224C11F400172E69 /* perf_spec_test.json in Resources */, + 54ACB6D1224C11F400172E69 /* persistence_spec_test.json in Resources */, + 54ACB6D2224C11F400172E69 /* query_spec_test.json in Resources */, + 54ACB6D3224C11F400172E69 /* remote_store_spec_test.json in Resources */, + 54ACB6D4224C11F400172E69 /* resume_token_spec_test.json in Resources */, + 54ACB6D5224C11F400172E69 /* write_spec_test.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54AA33B2224C0035006CE580 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54B8E4A8224BDC4100930F18 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 54C9EDEF2040E16300A969CD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1578,6 +2308,7 @@ 54DA12AC1F315EE100DD57A1 /* orderby_spec_test.json in Resources */, D5B25CBF07F65E885C9D68AB /* perf_spec_test.json in Resources */, 54DA12AD1F315EE100DD57A1 /* persistence_spec_test.json in Resources */, + 731541612214AFFA0037F4DC /* query_spec_test.json in Resources */, 3B843E4C1F3A182900548890 /* remote_store_spec_test.json in Resources */, 54DA12AE1F315EE100DD57A1 /* resume_token_spec_test.json in Resources */, 54DA12AF1F315EE100DD57A1 /* write_spec_test.json in Resources */, @@ -1614,13 +2345,41 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1B1BCDC6BB656D6B79D246DD /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/leveldb-library-tvOS/leveldb.framework", + "${BUILT_PRODUCTS_DIR}/GoogleTest-tvOS/GoogleTest.framework", + "${BUILT_PRODUCTS_DIR}/OCMock-tvOS/OCMock.framework", + "${BUILT_PRODUCTS_DIR}/ProtobufCpp-tvOS/ProtobufCpp.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleTest.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ProtobufCpp.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 1EE692C7509A98D7EB03CA51 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/BoringSSL-GRPC-iOS/openssl_grpc.framework", "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher-iOS/GTMSessionFetcher.framework", "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", @@ -1643,7 +2402,65 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 264B3405701AA9DC9F07658B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Firestore_Example_tvOS/Pods-Firestore_Example_tvOS-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/BoringSSL-GRPC-tvOS/openssl_grpc.framework", + "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher-tvOS/GTMSessionFetcher.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities-85d94889/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/Protobuf-tvOS10.0/Protobuf.framework", + "${BUILT_PRODUCTS_DIR}/gRPC-C++-tvOS/grpcpp.framework", + "${BUILT_PRODUCTS_DIR}/gRPC-Core-tvOS/grpc.framework", + "${BUILT_PRODUCTS_DIR}/leveldb-library-tvOS/leveldb.framework", + "${BUILT_PRODUCTS_DIR}/nanopb-tvOS/nanopb.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl_grpc.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpcpp.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpc.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_Example_tvOS/Pods-Firestore_Example_tvOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 30108B32BF2B385AECDB7FB2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Firestore_Tests_macOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 329C25E418360CEF62F6CB2B /* [CP] Embed Pods Frameworks */ = { @@ -1652,11 +2469,11 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/leveldb-library-iOS/leveldb.framework", - "${BUILT_PRODUCTS_DIR}/GoogleTest/GoogleTest.framework", - "${BUILT_PRODUCTS_DIR}/OCMock/OCMock.framework", - "${BUILT_PRODUCTS_DIR}/ProtobufCpp/ProtobufCpp.framework", + "${BUILT_PRODUCTS_DIR}/GoogleTest-iOS/GoogleTest.framework", + "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework", + "${BUILT_PRODUCTS_DIR}/ProtobufCpp-iOS/ProtobufCpp.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -1667,7 +2484,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 4C71ED5B5EF024AEF16B5E55 /* [CP] Embed Pods Frameworks */ = { @@ -1676,7 +2493,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/GoogleBenchmark/GoogleBenchmark.framework", ); name = "[CP] Embed Pods Frameworks"; @@ -1685,7 +2502,51 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 54D4C01B433CAC3C4EEDB1F9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Firestore_IntegrationTests_macOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 6800EBA4F597F7115445FCB5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Firestore_IntegrationTests_tvOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 6A86E48DF663B6AA1CB5BA83 /* [CP] Embed Pods Frameworks */ = { @@ -1694,11 +2555,11 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-macOS_example/Pods-macOS_example-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Firestore_Example_macOS/Pods-Firestore_Example_macOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/BoringSSL-GRPC-macOS/openssl_grpc.framework", "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher-macOS/GTMSessionFetcher.framework", - "${BUILT_PRODUCTS_DIR}/GoogleUtilities-Environment-Logger/GoogleUtilities.framework", - "${BUILT_PRODUCTS_DIR}/Protobuf-macOS10.10/Protobuf.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities-65885a2c/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/Protobuf-macOS10.11/Protobuf.framework", "${BUILT_PRODUCTS_DIR}/gRPC-C++-macOS/grpcpp.framework", "${BUILT_PRODUCTS_DIR}/gRPC-Core-macOS/grpc.framework", "${BUILT_PRODUCTS_DIR}/leveldb-library-macOS/leveldb.framework", @@ -1717,7 +2578,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-macOS_example/Pods-macOS_example-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_Example_macOS/Pods-Firestore_Example_macOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 6E622C7A20F52C8300B7E93A /* Run Script */ = { @@ -1761,7 +2622,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Protobuf-iOS9.0/Protobuf.framework", "${BUILT_PRODUCTS_DIR}/LibFuzzer/LibFuzzer.framework", ); @@ -1772,28 +2633,36 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 7C2467DCD3E3E16FB0A737DE /* [CP] Check Pods Manifest.lock */ = { + 76368D74F155BC9491DC124E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", + "${PODS_ROOT}/Target Support Files/Pods-Firestore_IntegrationTests_tvOS/Pods-Firestore_IntegrationTests_tvOS-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/leveldb-library-tvOS/leveldb.framework", + "${BUILT_PRODUCTS_DIR}/GoogleTest-tvOS/GoogleTest.framework", + "${BUILT_PRODUCTS_DIR}/OCMock-tvOS/OCMock.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( ); - name = "[CP] Check Pods Manifest.lock"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-macOS_example-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleTest.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_IntegrationTests_tvOS/Pods-Firestore_IntegrationTests_tvOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 83F2AB95D08093BB076EE521 /* [CP] Check Pods Manifest.lock */ = { + 7C2467DCD3E3E16FB0A737DE /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1804,23 +2673,91 @@ ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_iOS-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_macOS-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 8B469EB6DA9E6404589402E2 /* [CP] Check Pods Manifest.lock */ = { + 7E4A6E169B172874E17A3ECA /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", + "${PODS_ROOT}/Target Support Files/Pods-Firestore_Tests_macOS/Pods-Firestore_Tests_macOS-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/leveldb-library-macOS/leveldb.framework", + "${BUILT_PRODUCTS_DIR}/GoogleTest-macOS/GoogleTest.framework", + "${BUILT_PRODUCTS_DIR}/OCMock-macOS/OCMock.framework", + "${BUILT_PRODUCTS_DIR}/ProtobufCpp-macOS/ProtobufCpp.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( ); - name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleTest.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ProtobufCpp.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_Tests_macOS/Pods-Firestore_Tests_macOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 83F2AB95D08093BB076EE521 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_iOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8748E45246D96175497949A5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_tvOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8B469EB6DA9E6404589402E2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Firestore_Tests_iOS-checkManifestLockResult.txt", ); @@ -1829,6 +2766,28 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + A4274FBF1C966A0513CBD0F6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Firestore_Tests_tvOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; A827A009A65B69DC1B80EAD4 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1853,10 +2812,10 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/leveldb-library-iOS/leveldb.framework", - "${BUILT_PRODUCTS_DIR}/GoogleTest/GoogleTest.framework", - "${BUILT_PRODUCTS_DIR}/OCMock/OCMock.framework", + "${BUILT_PRODUCTS_DIR}/GoogleTest-iOS/GoogleTest.framework", + "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -1866,7 +2825,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; BF6384844477A4F850F0E89F /* [CP] Check Pods Manifest.lock */ = { @@ -1887,6 +2846,32 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + C164AD918C826AF88B418DA5 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Firestore_IntegrationTests_macOS/Pods-Firestore_IntegrationTests_macOS-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/leveldb-library-macOS/leveldb.framework", + "${BUILT_PRODUCTS_DIR}/GoogleTest-macOS/GoogleTest.framework", + "${BUILT_PRODUCTS_DIR}/OCMock-macOS/OCMock.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleTest.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_IntegrationTests_macOS/Pods-Firestore_IntegrationTests_macOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; D2D94DFA64939EF6DECDF908 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1911,7 +2896,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/BoringSSL-GRPC-iOS/openssl_grpc.framework", "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher-iOS/GTMSessionFetcher.framework", "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", @@ -1934,12 +2919,396 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 544AB18E2248072200F851E6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E11DDA3DD75705F26245E295 /* FIRCollectionReferenceTests.mm in Sources */, + 46999832F7D1709B4C29FAA8 /* FIRDocumentReferenceTests.mm in Sources */, + 6FD2369F24E884A9D767DD80 /* FIRDocumentSnapshotTests.mm in Sources */, + C39CBADA58F442C8D66C3DA2 /* FIRFieldPathTests.mm in Sources */, + F3F09BC931A717CEFF4E14B9 /* FIRFieldValueTests.mm in Sources */, + D59FAEE934987D4C4B2A67B2 /* FIRFirestoreTests.mm in Sources */, + 18CF41A17EA3292329E1119D /* FIRGeoPointTests.mm in Sources */, + 113190791F42202FDE1ABC14 /* FIRQuerySnapshotTests.mm in Sources */, + 38430E0E07C54FCD399AE919 /* FIRQueryTests.mm in Sources */, + 2EAD77559EC654E6CA4D3E21 /* FIRSnapshotMetadataTests.mm in Sources */, + CD0AA9E5D83C00CAAE7C2F67 /* FIRTimestampTest.m in Sources */, + 9D71628E38D9F64C965DF29E /* FSTAPIHelpers.mm in Sources */, + 10B7426844685A48234C093B /* FSTArraySortedDictionaryTests.m in Sources */, + B89EF6551734723BDC6AB79C /* FSTDocumentKeyTests.mm in Sources */, + F8D3EF0C9044BB3F3E81C6CA /* FSTDocumentSetTests.mm in Sources */, + E980E1DCF759D5EF9F6B98F2 /* FSTDocumentTests.mm in Sources */, + F4F00BF4E87D7F0F0F8831DB /* FSTEventAccumulator.mm in Sources */, + 21F821BF241244BA7BF070D9 /* FSTEventManagerTests.mm in Sources */, + F007A46BE03A01C077EFCBD8 /* FSTFieldValueTests.mm in Sources */, + 0A6FBE65A7FE048BAD562A15 /* FSTGoogleTestTests.mm in Sources */, + 939C898FE9D129F6A2EA259C /* FSTHelpers.mm in Sources */, + 6672B445E006A7708B8531ED /* FSTImmutableSortedDictionary+Testing.m in Sources */, + DEF036EA1ECEECD8E3ECC362 /* FSTImmutableSortedSet+Testing.m in Sources */, + C4055D868A38221B332CD03D /* FSTIntegrationTestCase.mm in Sources */, + 10CA552415BE0954221A1626 /* FSTLRUGarbageCollectorTests.mm in Sources */, + 939A15D3AD941CF7242DA9FA /* FSTLevelDBLRUGarbageCollectorTests.mm in Sources */, + A7470B7B2433264FFDCC7AC3 /* FSTLevelDBLocalStoreTests.mm in Sources */, + E9558682F8A4DD3E7C85C067 /* FSTLevelDBMigrationsTests.mm in Sources */, + 1E52635E55FD6FAB78FD29D8 /* FSTLevelDBMutationQueueTests.mm in Sources */, + A38F4AE525A87FDEA41DED47 /* FSTLevelDBQueryCacheTests.mm in Sources */, + 927D22C6D294B82D1580C48D /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */, + EC80A217F3D66EB0272B36B0 /* FSTLevelDBSpecTests.mm in Sources */, + 63BB61B6366E7F80C348419D /* FSTLevelDBTransactionTests.mm in Sources */, + 54080260D85A6F583E61DA1D /* FSTLocalSerializerTests.mm in Sources */, + 904DA0AE915C02154AE547FC /* FSTLocalStoreTests.mm in Sources */, + 34387C13A92D31B212BC0CA9 /* FSTMemoryLRUGarbageCollectorTests.mm in Sources */, + 3958F87E768E5CF40B87EF90 /* FSTMemoryLocalStoreTests.mm in Sources */, + 70D96C9129976DB01AC58BAC /* FSTMemoryMutationQueueTests.mm in Sources */, + 6B8806528FD3757D33D8B8AE /* FSTMemoryQueryCacheTests.mm in Sources */, + C5DEDF6148FD41B3000DDD5C /* FSTMemoryRemoteDocumentCacheTests.mm in Sources */, + 6FF2B680CC8631B06C7BD7AB /* FSTMemorySpecTests.mm in Sources */, + F3261CBFC169DB375A0D9492 /* FSTMockDatastore.mm in Sources */, + 38F973FA8ADEAFE9541C25EA /* FSTMutationQueueTests.mm in Sources */, + A6543DD0A56F8523C6D518E1 /* FSTMutationTests.mm in Sources */, + 8943A7C0750CEB0B98D21209 /* FSTPersistenceTestHelpers.mm in Sources */, + AD86162AC78673BA969F3467 /* FSTQueryCacheTests.mm in Sources */, + 550FB7562D0CF9C3E1984000 /* FSTQueryListenerTests.mm in Sources */, + 29FF9029315C3A9FB0E0D79E /* FSTQueryTests.mm in Sources */, + D063F56AC89E074F9AB05DD3 /* FSTRemoteDocumentCacheTests.mm in Sources */, + BCD9AEA4A890E804922BF72F /* FSTRemoteEventTests.mm in Sources */, + 0D67722B43147F775891EA43 /* FSTSerializerBetaTests.mm in Sources */, + A907244EE37BC32C8D82948E /* FSTSpecTests.mm in Sources */, + 072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */, + 156B042479C42DB2C3190C63 /* FSTTreeSortedDictionaryTests.m in Sources */, + 3021937CBABFD9270A051900 /* FSTViewSnapshotTest.mm in Sources */, + B67BB1DA1E247A87B4755C26 /* FSTViewTests.mm in Sources */, + 6DCA8E54E652B78EFF3EEDAC /* XCTestCase+Await.mm in Sources */, + 45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */, + FF3405218188DFCE586FB26B /* app_testing.mm in Sources */, + B192F30DECA8C28007F9B1D0 /* array_sorted_map_test.cc in Sources */, + 4F857404731D45F02C5EE4C3 /* async_queue_libdispatch_test.mm in Sources */, + 83A9CD3B6E791A860CE81FA1 /* async_queue_std_test.cc in Sources */, + 0B7B24194E2131F5C325FE0E /* async_queue_test.cc in Sources */, + 1733601ECCEA33E730DEAF45 /* autoid_test.cc in Sources */, + 0DAA255C2FEB387895ADEE12 /* bits_test.cc in Sources */, + 5556B648B9B1C2F79A706B4F /* common.pb.cc in Sources */, + 08D853C9D3A4DC919C55671A /* comparison_test.cc in Sources */, + AAA50E56B9A7EF3EFDA62172 /* create_noop_connectivity_monitor.cc in Sources */, + B49311BDE5EB6DF811E03C1B /* credentials_provider_test.cc in Sources */, + 9774A6C2AA02A12D80B34C3C /* database_id_test.cc in Sources */, + 11F8EE69182C9699E90A9E3D /* database_info_test.cc in Sources */, + 3F2DF1DDDF7F5830F0669992 /* datastore_test.mm in Sources */, + 62DA31B79FE97A90EEF28B0B /* delayed_constructor_test.cc in Sources */, + FF4FA5757D13A2B7CEE40F04 /* document.pb.cc in Sources */, + 5B62003FEA9A3818FDF4E2DD /* document_key_test.cc in Sources */, + 355A9171EF3F7AD44A9C60CB /* document_test.cc in Sources */, + 3BCEBA50E9678123245C0272 /* empty_credentials_provider_test.cc in Sources */, + AC6C1E57B18730428CB15E03 /* executor_libdispatch_test.mm in Sources */, + E7D415B8717701B952C344E5 /* executor_std_test.cc in Sources */, + 470A37727BBF516B05ED276A /* executor_test.cc in Sources */, + 2E0BBA7E627EB240BA11B0D0 /* exponential_backoff_test.cc in Sources */, + 135429EEF1D7FA9D1E329392 /* fake_credentials_provider.cc in Sources */, + 07B1E8C62772758BC82FEBEE /* field_mask_test.cc in Sources */, + D9366A834BFF13246DC3AF9E /* field_path_test.cc in Sources */, + 3D11B104A8F01F85180B38F6 /* field_transform_test.mm in Sources */, + 9D0E720F5A6DBD48FF325016 /* field_value_test.cc in Sources */, + 60C72F86D2231B1B6592A5E6 /* filesystem_test.cc in Sources */, + A61AE3D94C975A87EFA82ADA /* firebase_credentials_provider_test.mm in Sources */, + C5655568EC2A9F6B5E6F9141 /* firestore.pb.cc in Sources */, + B8062EBDB8E5B680E46A6DD1 /* geo_point_test.cc in Sources */, + 056542AD1D0F78E29E22EFA9 /* grpc_connection_test.cc in Sources */, + 4D98894EB5B3D778F5628456 /* grpc_stream_test.cc in Sources */, + 71DF9A27169F25383C762F85 /* grpc_stream_tester.cc in Sources */, + E6821243C510797EFFC7BCE2 /* grpc_streaming_reader_test.cc in Sources */, + 3DFBA7413965F3E6F366E923 /* grpc_unary_call_test.cc in Sources */, + A1F57CC739211F64F2E9232D /* hard_assert_test.cc in Sources */, + 9783FAEA4CF758E8C4C2D76E /* hashing_test.cc in Sources */, + E82F8EBBC8CC37299A459E73 /* hashing_test_apple.mm in Sources */, + 897F3C1936612ACB018CA1DD /* http.pb.cc in Sources */, + 036F975093414351FE952F08 /* index_manager_test.mm in Sources */, + E084921EFB7CF8CB1E950D6C /* iterator_adaptors_test.cc in Sources */, + 49C04B97AB282FFA82FD98CD /* latlng.pb.cc in Sources */, + E4C0CC7FB88D8F6CB1B972C6 /* leveldb_index_manager_test.mm in Sources */, + 568EC1C0F68A7B95E57C8C6C /* leveldb_key_test.cc in Sources */, + 66FAB8EAC012A3822BD4D0C9 /* leveldb_util_test.cc in Sources */, + 974FF09E6AFD24D5A39B898B /* local_serializer_test.cc in Sources */, + DBDC8E997E909804F1B43E92 /* log_test.cc in Sources */, + 12158DFCEE09D24B7988A340 /* maybe_document.pb.cc in Sources */, + D44DA2F61B854E8771E4E446 /* memory_index_manager_test.mm in Sources */, + C5F1E2220E30ED5EAC9ABD9E /* mutation.pb.cc in Sources */, + 1CC9BABDD52B2A1E37E2698D /* mutation_test.cc in Sources */, + 9720B8BD354CCB64C0C627E6 /* nanopb_string_test.cc in Sources */, + 051D3E20184AF195266EF678 /* no_document_test.cc in Sources */, + D572B4D4DBDD6B9235781646 /* objc_compatibility_apple_test.mm in Sources */, + 72AD91671629697074F2545B /* ordered_code_test.cc in Sources */, + DB7E9C5A59CCCDDB7F0C238A /* path_test.cc in Sources */, + 0455FC6E2A281BD755FD933A /* precondition_test.cc in Sources */, + 938F2AF6EC5CD0B839300DB0 /* query.pb.cc in Sources */, + 7EF540911720DAAF516BEDF0 /* query_test.cc in Sources */, + 37EC6C6EA9169BB99078CA96 /* reference_set_test.cc in Sources */, + C7F174164D7C55E35A526009 /* resource_path_test.cc in Sources */, + 4DAF501EE4B4DB79ED4239B0 /* secure_random_test.cc in Sources */, + D57F4CB3C92CE3D4DF329B78 /* serializer_test.cc in Sources */, + 5D45CC300ED037358EF33A8F /* snapshot_version_test.cc in Sources */, + 862B1AC9EDAB309BBF4FB18C /* sorted_map_test.cc in Sources */, + 4A62B708A6532DD45414DA3A /* sorted_set_test.cc in Sources */, + C9F96C511F45851D38EC449C /* status.pb.cc in Sources */, + 5493A425225F9990006DE7BA /* status_apple_test.mm in Sources */, + 4DC660A62BC2B6369DA5C563 /* status_test.cc in Sources */, + 74985DE2C7EF4150D7A455FD /* statusor_test.cc in Sources */, + C5C01A1FB216DA4BA8BF1A02 /* stream_test.mm in Sources */, + F9DC01FCBE76CD4F0453A67C /* strerror_test.cc in Sources */, + 5EFBAD082CB0F86CD0711979 /* string_apple_test.mm in Sources */, + 56D85436D3C864B804851B15 /* string_format_apple_test.mm in Sources */, + 1F998DDECB54A66222CC66AA /* string_format_test.cc in Sources */, + 8C39F6D4B3AA9074DF00CFB8 /* string_util_test.cc in Sources */, + 229D1A9381F698D71F229471 /* string_win_test.cc in Sources */, + 4A3FF3B16A39A5DC6B7EBA51 /* target.pb.cc in Sources */, + E764F0F389E7119220EB212C /* target_id_generator_test.cc in Sources */, + 32A95242C56A1A230231DB6A /* testutil.cc in Sources */, + ACC9369843F5ED3BD2284078 /* timestamp_test.cc in Sources */, + 2AAEABFD550255271E3BAC91 /* to_string_apple_test.mm in Sources */, + 1E2AE064CF32A604DC7BFD4D /* to_string_test.cc in Sources */, + 4F67086B5CC1787F612AE503 /* token_test.cc in Sources */, + 8DA258092DD856D829D973B5 /* transform_operations_test.mm in Sources */, + 5F19F66D8B01BA2B97579017 /* tree_sorted_map_test.cc in Sources */, + 16FE432587C1B40AF08613D2 /* type_traits_apple_test.mm in Sources */, + 16F52ECC6FA8A0587CD779EB /* user_test.cc in Sources */, + E3C0E5F834A82EEE9F8C4519 /* watch_change_test.mm in Sources */, + 53AB47E44D897C81A94031F6 /* write.pb.cc in Sources */, + 2E6E6164F44B9E3C6BB88313 /* xcgmock_test.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54AA338B224BF935006CE580 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 54AA3393224BF935006CE580 /* AppDelegate.m in Sources */, + 54AA3396224BF935006CE580 /* ViewController.m in Sources */, + 54AA339E224BF936006CE580 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54AA33A2224BFE09006CE580 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 00B7AFE2A7C158DD685EB5EE /* FIRCollectionReferenceTests.mm in Sources */, + 25FE27330996A59F31713A0C /* FIRDocumentReferenceTests.mm in Sources */, + 28E4B4A53A739AE2C9CF4159 /* FIRDocumentSnapshotTests.mm in Sources */, + 5FE047FE866758FD6A6A6478 /* FIRFieldPathTests.mm in Sources */, + A57EC303CD2D6AA4F4745551 /* FIRFieldValueTests.mm in Sources */, + 31BDB4CB0E7458C650A77ED0 /* FIRFirestoreTests.mm in Sources */, + D98A0B6007E271E32299C79D /* FIRGeoPointTests.mm in Sources */, + 17638F813B9B556FE7718C0C /* FIRQuerySnapshotTests.mm in Sources */, + 40708C00B429E39CB20BA0F1 /* FIRQueryTests.mm in Sources */, + ED420D8F49DA5C41EEF93913 /* FIRSnapshotMetadataTests.mm in Sources */, + 36E174A66C323891AEA16A2A /* FIRTimestampTest.m in Sources */, + 6E4854B19B120C6F0F8192CC /* FSTAPIHelpers.mm in Sources */, + F1661B1C5F3E30535FB65046 /* FSTArraySortedDictionaryTests.m in Sources */, + 94E5399FA5EA82CCB0549AB5 /* FSTDocumentKeyTests.mm in Sources */, + C13502E39B0AEF0FADDDA5F2 /* FSTDocumentSetTests.mm in Sources */, + 4E679B9AA80202184D459569 /* FSTDocumentTests.mm in Sources */, + 73E42D984FB36173A2BDA57C /* FSTEventAccumulator.mm in Sources */, + 3E0C71810093ADFBAD9B453F /* FSTEventManagerTests.mm in Sources */, + CA69FC4DF0C906183CF5DCE9 /* FSTFieldValueTests.mm in Sources */, + E375FBA0632EFB4D14C4E5A9 /* FSTGoogleTestTests.mm in Sources */, + F72DF72447EA7AB9D100816A /* FSTHelpers.mm in Sources */, + 8A0749707105A077728119C2 /* FSTImmutableSortedDictionary+Testing.m in Sources */, + C1B859FD314E866619683940 /* FSTImmutableSortedSet+Testing.m in Sources */, + AEBF3F80ACC01AA8A27091CD /* FSTIntegrationTestCase.mm in Sources */, + 9BD7DC8F5ADA0FE64AFAFA75 /* FSTLRUGarbageCollectorTests.mm in Sources */, + 55BDA39A16C4229A1AECB796 /* FSTLevelDBLRUGarbageCollectorTests.mm in Sources */, + F46394FAA186BC6D19213B59 /* FSTLevelDBLocalStoreTests.mm in Sources */, + FA63B7521A07F1EB2F999859 /* FSTLevelDBMigrationsTests.mm in Sources */, + 535F51F2FF2AB52A6E629091 /* FSTLevelDBMutationQueueTests.mm in Sources */, + 7E4218DB09B85F8E379C73CB /* FSTLevelDBQueryCacheTests.mm in Sources */, + 4C10843309CD11C455CF3B2B /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */, + 7495E3BAE536CD839EE20F31 /* FSTLevelDBSpecTests.mm in Sources */, + 59D1E0A722CE68E00A3F85AA /* FSTLevelDBTransactionTests.mm in Sources */, + 023829DB2198383927233318 /* FSTLocalSerializerTests.mm in Sources */, + 9328C93759C78A10FDBF68E0 /* FSTLocalStoreTests.mm in Sources */, + 29FDE0C0BA643E3804D8546C /* FSTMemoryLRUGarbageCollectorTests.mm in Sources */, + 3B47E82ED2A3C59AB5002640 /* FSTMemoryLocalStoreTests.mm in Sources */, + 1DFAEEE901B4E517A4CB9CAD /* FSTMemoryMutationQueueTests.mm in Sources */, + 4B52774AABF4ED7C2FA5C1C5 /* FSTMemoryQueryCacheTests.mm in Sources */, + 4247980BACA0070FB3E4A7A3 /* FSTMemoryRemoteDocumentCacheTests.mm in Sources */, + EB04FE18E5794FEC187A09E3 /* FSTMemorySpecTests.mm in Sources */, + 31D8E3D925FA3F70AA20ACCE /* FSTMockDatastore.mm in Sources */, + 239B9B357E67036BEA831E3A /* FSTMutationQueueTests.mm in Sources */, + 25CD471A28606A0DEE9F454A /* FSTMutationTests.mm in Sources */, + A6D29E15ED1221352DBE0CF2 /* FSTPersistenceTestHelpers.mm in Sources */, + BBFCCD960DD2937EE278D7B6 /* FSTQueryCacheTests.mm in Sources */, + 300D9D215F4128E69068B863 /* FSTQueryListenerTests.mm in Sources */, + CA18CEF2585A6BC4974DB56D /* FSTQueryTests.mm in Sources */, + 9664E5831CE35D515CDBC12A /* FSTRemoteDocumentCacheTests.mm in Sources */, + 61D1EB3438B92F61F6CAC191 /* FSTRemoteEventTests.mm in Sources */, + F58A4EE0A1A77F61EF41E5ED /* FSTSerializerBetaTests.mm in Sources */, + D5E9954FC1C5ABBC7A180B33 /* FSTSpecTests.mm in Sources */, + D69B97FF4C065EACEDD91886 /* FSTSyncEngineTestDriver.mm in Sources */, + 406939B62E5A6A22ADAB6FE6 /* FSTTreeSortedDictionaryTests.m in Sources */, + 4BB325E8B87A2FA4483AA070 /* FSTViewSnapshotTest.mm in Sources */, + 0265CCC8BBB76AE013F52411 /* FSTViewTests.mm in Sources */, + AAC15E7CCAE79619B2ABB972 /* XCTestCase+Await.mm in Sources */, + 1C19D796DB6715368407387A /* annotations.pb.cc in Sources */, + 6EEA00A737690EF82A3C91C6 /* app_testing.mm in Sources */, + 1291D9F5300AFACD1FBD262D /* array_sorted_map_test.cc in Sources */, + 4AD9809C9CE9FA09AC40992F /* async_queue_libdispatch_test.mm in Sources */, + 38208AC761FF994BA69822BE /* async_queue_std_test.cc in Sources */, + 900D0E9F18CE3DB954DD0D1E /* async_queue_test.cc in Sources */, + 5D5E24E3FA1128145AA117D2 /* autoid_test.cc in Sources */, + B6FDE6F91D3F81D045E962A0 /* bits_test.cc in Sources */, + 18638EAED9E126FC5D895B14 /* common.pb.cc in Sources */, + 1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */, + 169D01E6FF2CDF994B32B491 /* create_noop_connectivity_monitor.cc in Sources */, + 5686B35D611C1CFF6BFE7215 /* credentials_provider_test.cc in Sources */, + 58E377DCCC64FE7D2C6B59A1 /* database_id_test.cc in Sources */, + 8F3AE423677A4C50F7E0E5C0 /* database_info_test.cc in Sources */, + CEDDC6DB782989587D0139B2 /* datastore_test.mm in Sources */, + D22B96C19A0F3DE998D4320C /* delayed_constructor_test.cc in Sources */, + 25A75DFA730BAD21A5538EC5 /* document.pb.cc in Sources */, + D6E0E54CD1640E726900828A /* document_key_test.cc in Sources */, + 07A64E6C4EB700E3AF3FD496 /* document_test.cc in Sources */, + 2B1E95FAFD350C191B525F3B /* empty_credentials_provider_test.cc in Sources */, + B220E091D8F4E6DE1EA44F57 /* executor_libdispatch_test.mm in Sources */, + BAB43C839445782040657239 /* executor_std_test.cc in Sources */, + 3A7CB01751697ED599F2D9A1 /* executor_test.cc in Sources */, + EF3518F84255BAF3EBD317F6 /* exponential_backoff_test.cc in Sources */, + 4E8085FB9DBE40BAE11F0F4E /* fake_credentials_provider.cc in Sources */, + ED4E2AC80CAF2A8FDDAC3DEE /* field_mask_test.cc in Sources */, + 41EAC526C543064B8F3F7EDA /* field_path_test.cc in Sources */, + 4B57AE178F715ADE738C4F78 /* field_transform_test.mm in Sources */, + E4EEF6AAFCD33303CE9E5408 /* field_value_test.cc in Sources */, + AAF2F02E77A80C9CDE2C0C7A /* filesystem_test.cc in Sources */, + DAC43DD1FDFBAB1FE1AD6BE5 /* firebase_credentials_provider_test.mm in Sources */, + 8683BBC3AC7B01937606A83B /* firestore.pb.cc in Sources */, + F7718C43D3A8FCCDB4BB0071 /* geo_point_test.cc in Sources */, + BA9A65BD6D993B2801A3C768 /* grpc_connection_test.cc in Sources */, + D6DE74259F5C0CCA010D6A0D /* grpc_stream_test.cc in Sources */, + 7BBE0389D855242DDB83334B /* grpc_stream_tester.cc in Sources */, + 804B0C6CCE3933CF3948F249 /* grpc_streaming_reader_test.cc in Sources */, + 8612F3C7E4A7D17221442699 /* grpc_unary_call_test.cc in Sources */, + E0E640226A1439C59BBBA9C1 /* hard_assert_test.cc in Sources */, + 227CFA0B2A01884C277E4F1D /* hashing_test.cc in Sources */, + CD78EEAA1CD36BE691CA3427 /* hashing_test_apple.mm in Sources */, + 1357806B4CD3A62A8F5DE86D /* http.pb.cc in Sources */, + 20EC3A46525B8C3471D7D179 /* index_manager_test.mm in Sources */, + 0E4C94369FFF7EC0C9229752 /* iterator_adaptors_test.cc in Sources */, + 0FBDD5991E8F6CD5F8542474 /* latlng.pb.cc in Sources */, + A64B1CD2776BC118C74503A7 /* leveldb_index_manager_test.mm in Sources */, + B513F723728E923DFF34F60F /* leveldb_key_test.cc in Sources */, + 7A3BE0ED54933C234FDE23D1 /* leveldb_util_test.cc in Sources */, + 0FA4D5601BE9F0CB5EC2882C /* local_serializer_test.cc in Sources */, + 12BB9ED1CA98AA52B92F497B /* log_test.cc in Sources */, + 88FD82A1FC5FEC5D56B481D8 /* maybe_document.pb.cc in Sources */, + 01C0A2CF788A93EF2CEB6100 /* memory_index_manager_test.mm in Sources */, + 153F3E4E9E3A0174E29550B4 /* mutation.pb.cc in Sources */, + 5E6F9184B271F6D5312412FF /* mutation_test.cc in Sources */, + 78E38BEDF502B85E27D50C3B /* nanopb_string_test.cc in Sources */, + FEF55ECFB0CA317B351179AB /* no_document_test.cc in Sources */, + 7400AC9377419A28B782B5EC /* objc_compatibility_apple_test.mm in Sources */, + FD8EA96A604E837092ACA51D /* ordered_code_test.cc in Sources */, + 0963F6D7B0F9AE1E24B82866 /* path_test.cc in Sources */, + 152543FD706D5E8851C8DA92 /* precondition_test.cc in Sources */, + 5FA3DB52A478B01384D3A2ED /* query.pb.cc in Sources */, + F481368DB694B3B4D0C8E4A2 /* query_test.cc in Sources */, + 7DBE7DB90CF83B589A94980F /* reference_set_test.cc in Sources */, + 85B8918FC8C5DC62482E39C3 /* resource_path_test.cc in Sources */, + A8C9FF6D13E6C83D4AB54EA7 /* secure_random_test.cc in Sources */, + 31A396C81A107D1DEFDF4A34 /* serializer_test.cc in Sources */, + 13D8F4196528BAB19DBB18A7 /* snapshot_version_test.cc in Sources */, + 86E6FC2B7657C35B342E1436 /* sorted_map_test.cc in Sources */, + 8413BD9958F6DD52C466D70F /* sorted_set_test.cc in Sources */, + 0D2D25522A94AA8195907870 /* status.pb.cc in Sources */, + 5493A426225F9990006DE7BA /* status_apple_test.mm in Sources */, + C0AD8DB5A84CAAEE36230899 /* status_test.cc in Sources */, + DC48407370E87F2233D7AB7E /* statusor_test.cc in Sources */, + 215643858470A449D3A3E168 /* stream_test.mm in Sources */, + 69ED7BC38B3F981DE91E7933 /* strerror_test.cc in Sources */, + 0087625FD31D76E1365C589E /* string_apple_test.mm in Sources */, + 7A7EC216A0015D7620B4FF3E /* string_format_apple_test.mm in Sources */, + 392F527F144BADDAC69C5485 /* string_format_test.cc in Sources */, + E50187548B537DBCDBF7F9F0 /* string_util_test.cc in Sources */, + 81D1B1D2B66BD8310AC5707F /* string_win_test.cc in Sources */, + 81B23D2D4E061074958AF12F /* target.pb.cc in Sources */, + DA4303684707606318E1914D /* target_id_generator_test.cc in Sources */, + 8388418F43042605FB9BFB92 /* testutil.cc in Sources */, + 26CB3D7C871BC56456C6021E /* timestamp_test.cc in Sources */, + 5BE49546D57C43DDFCDB6FBD /* to_string_apple_test.mm in Sources */, + E500AB82DF2E7F3AFDB1AB3F /* to_string_test.cc in Sources */, + 2F6E23D7888FC82475C63010 /* token_test.cc in Sources */, + 5C7FAF228D0F52CFFE9E41B5 /* transform_operations_test.mm in Sources */, + 627253FDEC6BB5549FE77F4E /* tree_sorted_map_test.cc in Sources */, + 9AC28D928902C6767A11F5FC /* type_traits_apple_test.mm in Sources */, + 596C782EFB68131380F8EEF8 /* user_test.cc in Sources */, + 178FE1E277C63B3E7120BE56 /* watch_change_test.mm in Sources */, + A5AB1815C45FFC762981E481 /* write.pb.cc in Sources */, + 6EB896CD1B64A60E6C82D8CC /* xcgmock_test.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54AA33B0224C0035006CE580 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 95ED06D2B0078D3CDB821B68 /* FIRArrayTransformTests.mm in Sources */, + EC160876D8A42166440E0B53 /* FIRCursorTests.mm in Sources */, + EA38690795FBAA182A9AA63E /* FIRDatabaseTests.mm in Sources */, + 60985657831B8DDE2C65AC8B /* FIRFieldsTests.mm in Sources */, + BC2D0A8EA272A0058F6C2B9E /* FIRFirestoreSourceTests.mm in Sources */, + F19B749671F2552E964422F7 /* FIRListenerRegistrationTests.mm in Sources */, + 08F44F7DF9A3EF0D35C8FB57 /* FIRNumericTransformTests.mm in Sources */, + 9A29D572C64CA1FA62F591D4 /* FIRQueryTests.mm in Sources */, + FA7837C5CDFB273DE447E447 /* FIRServerTimestampTests.mm in Sources */, + 75D124966E727829A5F99249 /* FIRTypeTests.mm in Sources */, + 12DB753599571E24DCED0C2C /* FIRValidationTests.mm in Sources */, + BC0C98A9201E8F98B9A176A9 /* FIRWriteBatchTests.mm in Sources */, + A4ECA8335000CBDF94586C94 /* FSTDatastoreTests.mm in Sources */, + 2E169CF1E9E499F054BB873A /* FSTEventAccumulator.mm in Sources */, + 086E10B1B37666FB746D56BC /* FSTHelpers.mm in Sources */, + 02C953A7B0FA5EF87DB0361A /* FSTIntegrationTestCase.mm in Sources */, + 3D9619906F09108E34FF0C95 /* FSTSmokeTests.mm in Sources */, + D77941FD93DBE862AEF1F623 /* FSTTransactionTests.mm in Sources */, + 4D42E5C756229C08560DD731 /* XCTestCase+Await.mm in Sources */, + 7B8D7BAC1A075DB773230505 /* app_testing.mm in Sources */, + 409C0F2BFC2E1BECFFAC4D32 /* testutil.cc in Sources */, + 1EF47EF3D03B0847007C2318 /* xcgmock_test.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 54B8E4A6224BDC4100930F18 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 660E99DEDA0A6FC1CCB200F9 /* FIRArrayTransformTests.mm in Sources */, + A55266E6C986251D283CE948 /* FIRCursorTests.mm in Sources */, + 7DD67E9621C52B790E844B16 /* FIRDatabaseTests.mm in Sources */, + 8460C97C9209D7DAF07090BD /* FIRFieldsTests.mm in Sources */, + 8A6C809B9F81C30B7333FCAA /* FIRFirestoreSourceTests.mm in Sources */, + E2B15548A3B6796CE5A01975 /* FIRListenerRegistrationTests.mm in Sources */, + B03F286F3AEC3781C386C646 /* FIRNumericTransformTests.mm in Sources */, + 6A4F6B42C628D55CCE0C311F /* FIRQueryTests.mm in Sources */, + 27E46C94AAB087C80A97FF7F /* FIRServerTimestampTests.mm in Sources */, + 5F05A801B1EA44BC1264E55A /* FIRTypeTests.mm in Sources */, + 8403D519C916C72B9C7F2FA1 /* FIRValidationTests.mm in Sources */, + 8705C4856498F66E471A0997 /* FIRWriteBatchTests.mm in Sources */, + 4A64A339BCA77B9F875D1D8B /* FSTDatastoreTests.mm in Sources */, + 1C7254742A9F6F7042C9D78E /* FSTEventAccumulator.mm in Sources */, + 198F193BD9484E49375A7BE7 /* FSTHelpers.mm in Sources */, + 0F54634745BA07B09BDC14D7 /* FSTIntegrationTestCase.mm in Sources */, + 42063E6AE9ADF659AA6D4E18 /* FSTSmokeTests.mm in Sources */, + 5E5B3B8B3A41C8EB70035A6B /* FSTTransactionTests.mm in Sources */, + 736C4E82689F1CA1859C4A3F /* XCTestCase+Await.mm in Sources */, + 8F4F40E9BC7ED588F67734D5 /* app_testing.mm in Sources */, + A17DBC8F24127DA8A381F865 /* testutil.cc in Sources */, + A94884460990CD48CC0AD070 /* xcgmock_test.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 54C9EDED2040E16300A969CD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2043,6 +3412,7 @@ ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */, AB38D93020236E21000A432D /* database_info_test.cc in Sources */, 546854AA20A36867004BDBD5 /* datastore_test.mm in Sources */, + 6EC28BB8C38E3FD126F68211 /* delayed_constructor_test.cc in Sources */, 544129DD21C2DDC800EFB9CC /* document.pb.cc in Sources */, B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */, AB6B908420322E4D00CC290A /* document_test.cc in Sources */, @@ -2069,13 +3439,16 @@ 54511E8E209805F8005BD28F /* hashing_test.cc in Sources */, B69CF3F12227386500B281C8 /* hashing_test_apple.mm in Sources */, 618BBEB020B89AAC00B5BCE7 /* http.pb.cc in Sources */, + 73F1F73D2210F3D800E1F692 /* index_manager_test.mm in Sources */, 54A0353520A3D8CB003E0143 /* iterator_adaptors_test.cc in Sources */, 618BBEAE20B89AAC00B5BCE7 /* latlng.pb.cc in Sources */, + 73F1F7412211FEF300E1F692 /* leveldb_index_manager_test.mm in Sources */, 54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */, BEE0294A23AB993E5DE0E946 /* leveldb_util_test.cc in Sources */, 020AFD89BB40E5175838BB76 /* local_serializer_test.cc in Sources */, 54C2294F1FECABAE007D065B /* log_test.cc in Sources */, 618BBEA720B89AAC00B5BCE7 /* maybe_document.pb.cc in Sources */, + 73F1F73C2210F3D800E1F692 /* memory_index_manager_test.mm in Sources */, 618BBEA820B89AAC00B5BCE7 /* mutation.pb.cc in Sources */, 32F022CB75AEE48CDDAF2982 /* mutation_test.cc in Sources */, 84DBE646DCB49305879D3500 /* nanopb_string_test.cc in Sources */, @@ -2094,6 +3467,7 @@ 549CCA5220A36DBC00BCEB75 /* sorted_map_test.cc in Sources */, 549CCA5020A36DBC00BCEB75 /* sorted_set_test.cc in Sources */, 618BBEB120B89AAC00B5BCE7 /* status.pb.cc in Sources */, + 5493A424225F9990006DE7BA /* status_apple_test.mm in Sources */, 54A0352F20A3B3D8003E0143 /* status_test.cc in Sources */, 54A0353020A3B3D8003E0143 /* statusor_test.cc in Sources */, B66D8996213609EE0086DA0C /* stream_test.mm in Sources */, @@ -2116,6 +3490,7 @@ ABC1D7DE2023A05300BA84F0 /* user_test.cc in Sources */, B68FC0E521F6848700A7055C /* watch_change_test.mm in Sources */, 544129DE21C2DDC800EFB9CC /* write.pb.cc in Sources */, + 9794E074439ABE5457E60F35 /* xcgmock_test.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2163,12 +3538,33 @@ 5492E0442021457E00B64F25 /* XCTestCase+Await.mm in Sources */, EBFC611B1BF195D0EC710AF4 /* app_testing.mm in Sources */, CA989C0E6020C372A62B7062 /* testutil.cc in Sources */, + 4D1F46B2DD91198C8867C04C /* xcgmock_test.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 544AB1982248072200F851E6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DAFF0CF421E64AC30062958F /* Firestore_Example_macOS */; + targetProxy = 544AB1972248072200F851E6 /* PBXContainerItemProxy */; + }; + 54AA33AC224BFE0A006CE580 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 54AA338E224BF935006CE580 /* Firestore_Example_tvOS */; + targetProxy = 54AA33AB224BFE0A006CE580 /* PBXContainerItemProxy */; + }; + 54AA33BA224C0035006CE580 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 54AA338E224BF935006CE580 /* Firestore_Example_tvOS */; + targetProxy = 54AA33B9224C0035006CE580 /* PBXContainerItemProxy */; + }; + 54B8E4B0224BDC4100930F18 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DAFF0CF421E64AC30062958F /* Firestore_Example_macOS */; + targetProxy = 54B8E4AF224BDC4100930F18 /* PBXContainerItemProxy */; + }; 54C9EDF72040E16300A969CD /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 6003F589195388D20070C39A /* Firestore_Example_iOS */; @@ -2212,6 +3608,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 54AA3397224BF935006CE580 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 54AA3398224BF935006CE580 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; 6003F596195388D20070C39A /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -2228,25 +3632,643 @@ name = InfoPlist.strings; sourceTree = ""; }; - 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 71719F9E1E33DC2100824A3D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; + 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 71719F9E1E33DC2100824A3D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + DAFF0CFC21E64AC40062958F /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + DAFF0CFD21E64AC40062958F /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 544AB1992248072200F851E6 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BD01F0E43E4E2A07B8B05099 /* Pods-Firestore_Tests_macOS.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = c99; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/nanopb\"", + "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", + "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", + "\"${PODS_ROOT}/ProtobufCpp/src\"", + ); + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-l\"z\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"GTMSessionFetcher\"", + "-framework", + "\"GoogleTest\"", + "-framework", + "\"GoogleUtilities\"", + "-framework", + "\"OCMock\"", + "-framework", + "\"Protobuf\"", + "-framework", + "\"ProtobufCpp\"", + "-framework", + "\"Security\"", + "-framework", + "\"SystemConfiguration\"", + "-framework", + "\"grpc\"", + "-framework", + "\"grpcpp\"", + "-framework", + "\"leveldb\"", + "-framework", + "\"nanopb\"", + "-framework", + "\"openssl_grpc\"", + "-framework", + "\"FirebaseFirestore\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-Tests-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SYSTEM_HEADER_SEARCH_PATHS = "\"${PODS_ROOT}/nanopb\""; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_macOS.app/Contents/MacOS/Firestore_Example_macOS"; + }; + name = Debug; + }; + 544AB19A2248072200F851E6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 397FB002E298B780F1E223E2 /* Pods-Firestore_Tests_macOS.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = c99; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/nanopb\"", + "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", + "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", + "\"${PODS_ROOT}/ProtobufCpp/src\"", + ); + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-l\"z\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"GTMSessionFetcher\"", + "-framework", + "\"GoogleTest\"", + "-framework", + "\"GoogleUtilities\"", + "-framework", + "\"OCMock\"", + "-framework", + "\"Protobuf\"", + "-framework", + "\"ProtobufCpp\"", + "-framework", + "\"Security\"", + "-framework", + "\"SystemConfiguration\"", + "-framework", + "\"grpc\"", + "-framework", + "\"grpcpp\"", + "-framework", + "\"leveldb\"", + "-framework", + "\"nanopb\"", + "-framework", + "\"openssl_grpc\"", + "-framework", + "\"FirebaseFirestore\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-Tests-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SYSTEM_HEADER_SEARCH_PATHS = "\"${PODS_ROOT}/nanopb\""; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_macOS.app/Contents/MacOS/Firestore_Example_macOS"; + }; + name = Release; + }; + 54AA339F224BF936006CE580 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A70E82DD627B162BEF92B8ED /* Pods-Firestore_Example_tvOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_C_LANGUAGE_STANDARD = c99; + INFOPLIST_FILE = "$(SRCROOT)/App/tvOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-Example-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; + 54AA33A0224BF936006CE580 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FC738525340E594EBFAB121E /* Pods-Firestore_Example_tvOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = c99; + INFOPLIST_FILE = "$(SRCROOT)/App/tvOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-Example-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; + }; + 54AA33AD224BFE0A006CE580 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2E48431B0EDA400BEA91D4AB /* Pods-Firestore_Tests_tvOS.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_C_LANGUAGE_STANDARD = c99; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/nanopb\"", + "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", + "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", + "\"${PODS_ROOT}/ProtobufCpp/src\"", + ); + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-l\"z\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"GTMSessionFetcher\"", + "-framework", + "\"GoogleTest\"", + "-framework", + "\"GoogleUtilities\"", + "-framework", + "\"OCMock\"", + "-framework", + "\"Protobuf\"", + "-framework", + "\"ProtobufCpp\"", + "-framework", + "\"Security\"", + "-framework", + "\"grpc\"", + "-framework", + "\"grpcpp\"", + "-framework", + "\"leveldb\"", + "-framework", + "\"nanopb\"", + "-framework", + "\"openssl_grpc\"", + "-framework", + "\"FirebaseFirestore\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-Tests-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_tvOS.app/Firestore_Example_tvOS"; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; + 54AA33AE224BFE0A006CE580 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6AE927CDFC7A72BF825BE4CB /* Pods-Firestore_Tests_tvOS.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = c99; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/nanopb\"", + "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", + "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", + "\"${PODS_ROOT}/ProtobufCpp/src\"", + ); + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-l\"z\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"GTMSessionFetcher\"", + "-framework", + "\"GoogleTest\"", + "-framework", + "\"GoogleUtilities\"", + "-framework", + "\"OCMock\"", + "-framework", + "\"Protobuf\"", + "-framework", + "\"ProtobufCpp\"", + "-framework", + "\"Security\"", + "-framework", + "\"grpc\"", + "-framework", + "\"grpcpp\"", + "-framework", + "\"leveldb\"", + "-framework", + "\"nanopb\"", + "-framework", + "\"openssl_grpc\"", + "-framework", + "\"FirebaseFirestore\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-Tests-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_tvOS.app/Firestore_Example_tvOS"; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; + }; + 54AA33BC224C0035006CE580 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 74AC2ADBF1BAD9A8EF30CF41 /* Pods-Firestore_IntegrationTests_tvOS.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_C_LANGUAGE_STANDARD = c99; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", + "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", + ); + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-l\"z\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"GTMSessionFetcher\"", + "-framework", + "\"GoogleTest\"", + "-framework", + "\"GoogleUtilities\"", + "-framework", + "\"OCMock\"", + "-framework", + "\"Protobuf\"", + "-framework", + "\"Security\"", + "-framework", + "\"grpc\"", + "-framework", + "\"grpcpp\"", + "-framework", + "\"leveldb\"", + "-framework", + "\"nanopb\"", + "-framework", + "\"openssl_grpc\"", + "-framework", + "\"FirebaseFirestore\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-IntegrationTests-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_tvOS.app/Firestore_Example_tvOS"; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; + 54AA33BD224C0035006CE580 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 36D235D9F1240D5195CDB670 /* Pods-Firestore_IntegrationTests_tvOS.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = c99; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", + "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", + ); + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-l\"z\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"GTMSessionFetcher\"", + "-framework", + "\"GoogleTest\"", + "-framework", + "\"GoogleUtilities\"", + "-framework", + "\"OCMock\"", + "-framework", + "\"Protobuf\"", + "-framework", + "\"Security\"", + "-framework", + "\"grpc\"", + "-framework", + "\"grpcpp\"", + "-framework", + "\"leveldb\"", + "-framework", + "\"nanopb\"", + "-framework", + "\"openssl_grpc\"", + "-framework", + "\"FirebaseFirestore\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-IntegrationTests-tvOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_tvOS.app/Firestore_Example_tvOS"; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; }; - DAFF0CFC21E64AC40062958F /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - DAFF0CFD21E64AC40062958F /* Base */, - ); - name = MainMenu.xib; - sourceTree = ""; + 54B8E4B1224BDC4100930F18 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2F901F31BC62444A476B779F /* Pods-Firestore_IntegrationTests_macOS.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = c99; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", + "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", + ); + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-l\"z\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"GTMSessionFetcher\"", + "-framework", + "\"GoogleTest\"", + "-framework", + "\"GoogleUtilities\"", + "-framework", + "\"OCMock\"", + "-framework", + "\"Protobuf\"", + "-framework", + "\"Security\"", + "-framework", + "\"SystemConfiguration\"", + "-framework", + "\"grpc\"", + "-framework", + "\"grpcpp\"", + "-framework", + "\"leveldb\"", + "-framework", + "\"nanopb\"", + "-framework", + "\"openssl_grpc\"", + "-framework", + FirebaseFirestore, + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-IntegrationTests-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_macOS.app/Contents/MacOS/Firestore_Example_macOS"; + }; + name = Debug; + }; + 54B8E4B2224BDC4100930F18 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B953604968FBF5483BD20F5A /* Pods-Firestore_IntegrationTests_macOS.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = c99; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", + "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", + ); + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-l\"z\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"GTMSessionFetcher\"", + "-framework", + "\"GoogleTest\"", + "-framework", + "\"GoogleUtilities\"", + "-framework", + "\"OCMock\"", + "-framework", + "\"Protobuf\"", + "-framework", + "\"Security\"", + "-framework", + "\"SystemConfiguration\"", + "-framework", + "\"grpc\"", + "-framework", + "\"grpcpp\"", + "-framework", + "\"leveldb\"", + "-framework", + "\"nanopb\"", + "-framework", + "\"openssl_grpc\"", + "-framework", + FirebaseFirestore, + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-IntegrationTests-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_macOS.app/Contents/MacOS/Firestore_Example_macOS"; + }; + name = Release; }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ 54C9EDF82040E16300A969CD /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */; @@ -2483,19 +4505,32 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = c99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -2525,18 +4560,31 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = c99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -2547,6 +4595,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; OTHER_CFLAGS = ""; SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -2621,12 +4670,12 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/nanopb\"", "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", "\"${PODS_ROOT}/GoogleTest/googletest/include\"", "\"${PODS_ROOT}/leveldb-library/include\"", - "\"${PODS_ROOT}/../../../Firestore/Protos/nanopb\"", - "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"", "\"${PODS_ROOT}/ProtobufCpp/src\"", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; @@ -2699,12 +4748,12 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_ROOT}/../../..\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"", + "\"${PODS_ROOT}/../../../Firestore/Protos/nanopb\"", "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", "\"${PODS_ROOT}/GoogleTest/googletest/include\"", "\"${PODS_ROOT}/leveldb-library/include\"", - "\"${PODS_ROOT}/../../../Firestore/Protos/nanopb\"", - "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"", "\"${PODS_ROOT}/ProtobufCpp/src\"", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; @@ -2824,7 +4873,7 @@ }; DAFF0D0321E64AC40062958F /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 98366480BD1FD44A1FEDD982 /* Pods-macOS_example.debug.xcconfig */; + baseConfigurationReference = 98366480BD1FD44A1FEDD982 /* Pods-Firestore_Example_macOS.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; @@ -2844,18 +4893,16 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; - CODE_SIGN_ENTITLEMENTS = App/macOS_example/macOS_example.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 63PT2H4G8K; + DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = c99; GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = "$(SRCROOT)/App/macOS_example/Info.plist"; + INFOPLIST_FILE = "$(SRCROOT)/App/macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -2887,8 +4934,6 @@ "\"leveldb\"", "-framework", "\"nanopb\"", - "-framework", - "\"openssl\"", "-ObjC", ); PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-macOS"; @@ -2899,7 +4944,7 @@ }; DAFF0D0421E64AC40062958F /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DF148C0D5EEC4A2CD9FA484C /* Pods-macOS_example.release.xcconfig */; + baseConfigurationReference = DF148C0D5EEC4A2CD9FA484C /* Pods-Firestore_Example_macOS.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; @@ -2919,19 +4964,17 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; - CODE_SIGN_ENTITLEMENTS = App/macOS_example/macOS_example.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = 63PT2H4G8K; + DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = c99; GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = "$(SRCROOT)/App/macOS_example/Info.plist"; + INFOPLIST_FILE = "$(SRCROOT)/App/macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -2963,8 +5006,6 @@ "\"leveldb\"", "-framework", "\"nanopb\"", - "-framework", - "\"openssl\"", "-ObjC", ); PRODUCT_BUNDLE_IDENTIFIER = "com.google.Firestore-macOS"; @@ -2995,8 +5036,9 @@ "$(inherited)", "\"${PODS_ROOT}/../../..\"", "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", - "\"${PODS_ROOT}/leveldb-library/include\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; OTHER_LDFLAGS = ( @@ -3032,8 +5074,9 @@ "$(inherited)", "\"${PODS_ROOT}/../../..\"", "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"", - "\"${PODS_ROOT}/leveldb-library/include\"", + "\"${PODS_ROOT}/GoogleTest/googlemock/include\"", "\"${PODS_ROOT}/GoogleTest/googletest/include\"", + "\"${PODS_ROOT}/leveldb-library/include\"", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; OTHER_LDFLAGS = ( @@ -3066,6 +5109,51 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 544AB19B2248072200F851E6 /* Build configuration list for PBXNativeTarget "Firestore_Tests_macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 544AB1992248072200F851E6 /* Debug */, + 544AB19A2248072200F851E6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 54AA33A1224BF936006CE580 /* Build configuration list for PBXNativeTarget "Firestore_Example_tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 54AA339F224BF936006CE580 /* Debug */, + 54AA33A0224BF936006CE580 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 54AA33AF224BFE0A006CE580 /* Build configuration list for PBXNativeTarget "Firestore_Tests_tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 54AA33AD224BFE0A006CE580 /* Debug */, + 54AA33AE224BFE0A006CE580 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 54AA33BB224C0035006CE580 /* Build configuration list for PBXNativeTarget "Firestore_IntegrationTests_tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 54AA33BC224C0035006CE580 /* Debug */, + 54AA33BD224C0035006CE580 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 54B8E4B3224BDC4100930F18 /* Build configuration list for PBXNativeTarget "Firestore_IntegrationTests_macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 54B8E4B1224BDC4100930F18 /* Debug */, + 54B8E4B2224BDC4100930F18 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 54C9EDFA2040E16300A969CD /* Build configuration list for PBXNativeTarget "Firestore_SwiftTests_iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3120,7 +5208,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DAFF0D0521E64AC40062958F /* Build configuration list for PBXNativeTarget "macOS_example" */ = { + DAFF0D0521E64AC40062958F /* Build configuration list for PBXNativeTarget "Firestore_Example_macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( DAFF0D0321E64AC40062958F /* Debug */, diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/AllTests_iOS.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/AllTests_iOS.xcscheme index 69c49567444..325de9bfba8 100644 --- a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/AllTests_iOS.xcscheme +++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/AllTests_iOS.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Example_tvOS.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Example_tvOS.xcscheme new file mode 100644 index 00000000000..528621459ce --- /dev/null +++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Example_tvOS.xcscheme @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_FuzzTests_iOS.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_FuzzTests_iOS.xcscheme index 763229ed44c..e8a9b68bcbd 100644 --- a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_FuzzTests_iOS.xcscheme +++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_FuzzTests_iOS.xcscheme @@ -1,14 +1,17 @@ + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + + + + @@ -50,6 +62,15 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> + + + + + + + + diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_IntegrationTests_iOS.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_IntegrationTests_iOS.xcscheme index 4e6f82845e2..03e9133eefe 100644 --- a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_IntegrationTests_iOS.xcscheme +++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_IntegrationTests_iOS.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_IntegrationTests_tvOS.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_IntegrationTests_tvOS.xcscheme new file mode 100644 index 00000000000..3a921c69dde --- /dev/null +++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_IntegrationTests_tvOS.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_SwiftTests_iOS.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_SwiftTests_iOS.xcscheme index e53edc9da89..c56883edcdc 100644 --- a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_SwiftTests_iOS.xcscheme +++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_SwiftTests_iOS.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Tests_tvOS.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Tests_tvOS.xcscheme new file mode 100644 index 00000000000..e33360ffaa2 --- /dev/null +++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_Tests_tvOS.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Firestore/Example/GoogleBenchmark.podspec b/Firestore/Example/GoogleBenchmark.podspec index d29e77048da..1046b988d2d 100644 --- a/Firestore/Example/GoogleBenchmark.podspec +++ b/Firestore/Example/GoogleBenchmark.podspec @@ -34,6 +34,9 @@ Google's C++ benchmark framework. } s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.11' + s.tvos.deployment_target = '10.0' + s.requires_arc = false s.public_header_files = [ diff --git a/Firestore/Example/GoogleTest.podspec b/Firestore/Example/GoogleTest.podspec index 0ecba3d6aa7..8feab54a195 100644 --- a/Firestore/Example/GoogleTest.podspec +++ b/Firestore/Example/GoogleTest.podspec @@ -34,6 +34,9 @@ Google's C++ test framework. } s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.11' + s.tvos.deployment_target = '10.0' + s.requires_arc = false # Exclude include/gtest/internal/custom files from public headers. These diff --git a/Firestore/Example/LibFuzzer.podspec b/Firestore/Example/LibFuzzer.podspec index 16e9a1e74d7..569b6116714 100644 --- a/Firestore/Example/LibFuzzer.podspec +++ b/Firestore/Example/LibFuzzer.podspec @@ -31,6 +31,8 @@ Pod::Spec.new do |s| # LibFuzzer uses thread_local which does not appear to be supported before # iOS 9. s.ios.deployment_target = '9.0' + s.osx.deployment_target = '10.11' + s.tvos.deployment_target = '10.0' # Check out only libFuzzer folder. s.source = { diff --git a/Firestore/Example/Podfile b/Firestore/Example/Podfile index 9d719eb0f9b..09b8150fe13 100644 --- a/Firestore/Example/Podfile +++ b/Firestore/Example/Podfile @@ -3,18 +3,47 @@ #source 'https://github.com/CocoaPods/Specs.git' # Uncomment the next two lines for pre-release testing on public repo -#source 'https://github.com/Firebase/SpecsStaging.git' -#source 'https://github.com/CocoaPods/Specs.git' +source 'https://github.com/Firebase/SpecsStaging.git' +source 'https://github.com/CocoaPods/Specs.git' use_frameworks! + +# Appends the given +paths+ to the HEADER_SEARCH_PATHS setting for the given +# target in all build configurations. +def append_header_search_path(target, *paths) + setting = 'HEADER_SEARCH_PATHS' + target.build_configurations.each do |config| + config.build_settings[setting] ||= '$(inherited)' + paths.each do |path| + config.build_settings[setting] << ' ' + config.build_settings[setting] << path + end + end +end + + +post_install do |installer| + installer.pods_project.targets.each do |target| + # Building the FirebaseFirestore framework proper seems fragile in Xcode: + # changes to public headers seem to cause weird cascading failures in the + # IDE's pre-build error detection. The issue seems to be that the Pod's + # public headers are found by clang at build time through a copy headers + # phase, but the interactive editor won't have those in place, especially + # when the build is broken by current edits. + if target.name =~ /^FirebaseFirestore/ + append_header_search_path(target, '$(PODS_ROOT)/../../../Firestore/Source/Public') + end + end +end + target 'Firestore_Example_iOS' do platform :ios, '8.0' # The next line is the forcing function for the Firebase pod. The Firebase # version's subspecs should depend on the component versions in their # corresponding podspec's. - pod 'Firebase/CoreOnly', '5.18.0' + pod 'Firebase/CoreOnly', '6.0.0-pre' pod 'FirebaseAuth', :path => '../../' pod 'FirebaseAuthInterop', :path => '../../' @@ -60,10 +89,56 @@ target 'Firestore_Example_iOS' do end end -target 'macOS_example' do - platform :osx, '10.10' +target 'Firestore_Example_macOS' do + platform :osx, '10.11' pod 'FirebaseAuth', :path => '../../' pod 'FirebaseCore', :path => '../../' pod 'FirebaseFirestore', :path => '../../' + + target 'Firestore_Tests_macOS' do + inherit! :search_paths + + pod 'GoogleTest', :podspec => 'GoogleTest.podspec' + pod 'ProtobufCpp', :podspec => 'ProtobufCpp.podspec' + + pod 'OCMock' + pod 'leveldb-library' + end + + target 'Firestore_IntegrationTests_macOS' do + inherit! :search_paths + + pod 'GoogleTest', :podspec => 'GoogleTest.podspec' + + pod 'OCMock' + pod 'leveldb-library' + end +end + +target 'Firestore_Example_tvOS' do + platform :tvos, '10.0' + + pod 'FirebaseAuth', :path => '../../' + pod 'FirebaseCore', :path => '../../' + pod 'FirebaseFirestore', :path => '../../' + + target 'Firestore_Tests_tvOS' do + inherit! :search_paths + + pod 'GoogleTest', :podspec => 'GoogleTest.podspec' + pod 'ProtobufCpp', :podspec => 'ProtobufCpp.podspec' + + pod 'OCMock' + pod 'leveldb-library' + end + + target 'Firestore_IntegrationTests_tvOS' do + inherit! :search_paths + + pod 'GoogleTest', :podspec => 'GoogleTest.podspec' + + pod 'OCMock' + pod 'leveldb-library' + end end diff --git a/Firestore/Example/ProtobufCpp.podspec b/Firestore/Example/ProtobufCpp.podspec index c809c0690d5..14721069a1f 100644 --- a/Firestore/Example/ProtobufCpp.podspec +++ b/Firestore/Example/ProtobufCpp.podspec @@ -29,6 +29,10 @@ Pod::Spec.new do |s| :tag => "v#{s.version}" } + s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.11' + s.tvos.deployment_target = '10.0' + s.source_files = 'src/**/*.{h,cc}' s.exclude_files = # skip test files. (Yes, the test files are intermixed with # the source. No there doesn't seem to be a common/simple diff --git a/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm index 05cc94115aa..10384de4022 100644 --- a/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm +++ b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm @@ -25,46 +25,39 @@ #import "Firestore/Example/Tests/Util/FSTHelpers.h" #import "Firestore/Source/API/FIRDocumentChange+Internal.h" #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; using firebase::firestore::core::DocumentViewChange; using firebase::firestore::core::ViewSnapshot; using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentSet; NS_ASSUME_NONNULL_BEGIN -@interface FIRDocumentChange () - -// Expose initializer for testing. -- (instancetype)initWithType:(FIRDocumentChangeType)type - document:(FIRQueryDocumentSnapshot *)document - oldIndex:(NSUInteger)oldIndex - newIndex:(NSUInteger)newIndex; - -@end - @interface FIRQuerySnapshotTests : XCTestCase @end @implementation FIRQuerySnapshotTests - (void)testEquals { - FIRQuerySnapshot *foo = FSTTestQuerySnapshot("foo", @{}, @{@"a" : @{@"a" : @1}}, YES, NO); - FIRQuerySnapshot *fooDup = FSTTestQuerySnapshot("foo", @{}, @{@"a" : @{@"a" : @1}}, YES, NO); + FIRQuerySnapshot *foo = FSTTestQuerySnapshot("foo", @{}, @{@"a" : @{@"a" : @1}}, true, false); + FIRQuerySnapshot *fooDup = FSTTestQuerySnapshot("foo", @{}, @{@"a" : @{@"a" : @1}}, true, false); FIRQuerySnapshot *differentPath = - FSTTestQuerySnapshot("bar", @{}, @{@"a" : @{@"a" : @1}}, YES, NO); + FSTTestQuerySnapshot("bar", @{}, @{@"a" : @{@"a" : @1}}, true, false); FIRQuerySnapshot *differentDoc = - FSTTestQuerySnapshot("foo", @{@"a" : @{@"b" : @1}}, @{}, YES, NO); + FSTTestQuerySnapshot("foo", @{@"a" : @{@"b" : @1}}, @{}, true, false); FIRQuerySnapshot *noPendingWrites = - FSTTestQuerySnapshot("foo", @{}, @{@"a" : @{@"a" : @1}}, NO, NO); - FIRQuerySnapshot *fromCache = FSTTestQuerySnapshot("foo", @{}, @{@"a" : @{@"a" : @1}}, YES, YES); + FSTTestQuerySnapshot("foo", @{}, @{@"a" : @{@"a" : @1}}, false, false); + FIRQuerySnapshot *fromCache = + FSTTestQuerySnapshot("foo", @{}, @{@"a" : @{@"a" : @1}}, true, true); XCTAssertEqualObjects(foo, fooDup); XCTAssertNotEqualObjects(foo, differentPath); XCTAssertNotEqualObjects(foo, differentDoc); @@ -85,58 +78,43 @@ - (void)testIncludeMetadataChanges { FSTDocument *doc2Old = FSTTestDoc("foo/baz", 1, @{@"a" : @"b"}, FSTDocumentStateSynced); FSTDocument *doc2New = FSTTestDoc("foo/baz", 1, @{@"a" : @"c"}, FSTDocumentStateSynced); - FSTDocumentSet *oldDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[ doc1Old, doc2Old ]); - FSTDocumentSet *newDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[ doc2New, doc2New ]); + DocumentSet oldDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[ doc1Old, doc2Old ]); + DocumentSet newDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[ doc2New, doc2New ]); std::vector documentChanges{ - DocumentViewChange{doc1New, DocumentViewChange::Type::kMetadata}, - DocumentViewChange{doc2New, DocumentViewChange::Type::kModified}, + DocumentViewChange(doc1New, DocumentViewChange::Type::kMetadata), + DocumentViewChange(doc2New, DocumentViewChange::Type::kModified), }; - FIRFirestore *firestore = FSTTestFirestore(); + Firestore *firestore = FSTTestFirestore().wrapped; FSTQuery *query = FSTTestQuery("foo"); - ViewSnapshot viewSnapshot{query, - newDocuments, - oldDocuments, - std::move(documentChanges), - /*mutated_keys=*/DocumentKeySet{}, + ViewSnapshot viewSnapshot(query, newDocuments, oldDocuments, std::move(documentChanges), + /*mutated_keys=*/DocumentKeySet(), /*from_cache=*/false, /*sync_state_changed=*/true, - /*excludes_metadata_changes=*/false}; - FIRSnapshotMetadata *metadata = [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:NO - fromCache:NO]; - FIRQuerySnapshot *snapshot = [FIRQuerySnapshot snapshotWithFirestore:firestore - originalQuery:query - snapshot:std::move(viewSnapshot) - metadata:metadata]; - - FIRQueryDocumentSnapshot *doc1Snap = [FIRQueryDocumentSnapshot snapshotWithFirestore:firestore - documentKey:doc1New.key - document:doc1New - fromCache:NO - hasPendingWrites:NO]; - FIRQueryDocumentSnapshot *doc2Snap = [FIRQueryDocumentSnapshot snapshotWithFirestore:firestore - documentKey:doc2New.key - document:doc2New - fromCache:NO - hasPendingWrites:NO]; + /*excludes_metadata_changes=*/false); + SnapshotMetadata metadata(/*pending_writes=*/false, /*from_cache=*/false); + FIRQuerySnapshot *snapshot = [[FIRQuerySnapshot alloc] initWithFirestore:firestore + originalQuery:query + snapshot:std::move(viewSnapshot) + metadata:std::move(metadata)]; + + DocumentSnapshot doc1Snap(firestore, doc1New.key, doc1New, SnapshotMetadata()); + DocumentSnapshot doc2Snap(firestore, doc2New.key, doc2New, SnapshotMetadata()); NSArray *changesWithoutMetadata = @[ - [[FIRDocumentChange alloc] initWithType:FIRDocumentChangeTypeModified - document:doc2Snap - oldIndex:1 - newIndex:1], + [[FIRDocumentChange alloc] + initWithDocumentChange:DocumentChange(DocumentChange::Type::Modified, doc2Snap, + /*old_index=*/1, /*new_index=*/1)], ]; XCTAssertEqualObjects(snapshot.documentChanges, changesWithoutMetadata); NSArray *changesWithMetadata = @[ - [[FIRDocumentChange alloc] initWithType:FIRDocumentChangeTypeModified - document:doc1Snap - oldIndex:0 - newIndex:0], - [[FIRDocumentChange alloc] initWithType:FIRDocumentChangeTypeModified - document:doc2Snap - oldIndex:1 - newIndex:1], + [[FIRDocumentChange alloc] + initWithDocumentChange:DocumentChange(DocumentChange::Type::Modified, doc1Snap, + /*old_index=*/0, /*new_index=*/0)], + [[FIRDocumentChange alloc] + initWithDocumentChange:DocumentChange(DocumentChange::Type::Modified, doc2Snap, + /*old_index=*/1, /*new_index=*/1)], ]; XCTAssertEqualObjects([snapshot documentChangesWithIncludeMetadataChanges:YES], changesWithMetadata); diff --git a/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.mm b/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.mm index f705aa76284..442406dca14 100644 --- a/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.mm +++ b/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.mm @@ -28,14 +28,11 @@ @interface FIRSnapshotMetadataTests : XCTestCase @implementation FIRSnapshotMetadataTests - (void)testEquals { - FIRSnapshotMetadata *foo = [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:YES - fromCache:YES]; - FIRSnapshotMetadata *fooDup = [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:YES - fromCache:YES]; - FIRSnapshotMetadata *bar = [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:YES - fromCache:NO]; - FIRSnapshotMetadata *baz = [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:NO - fromCache:YES]; + FIRSnapshotMetadata *foo = [[FIRSnapshotMetadata alloc] initWithPendingWrites:YES fromCache:YES]; + FIRSnapshotMetadata *fooDup = [[FIRSnapshotMetadata alloc] initWithPendingWrites:YES + fromCache:YES]; + FIRSnapshotMetadata *bar = [[FIRSnapshotMetadata alloc] initWithPendingWrites:YES fromCache:NO]; + FIRSnapshotMetadata *baz = [[FIRSnapshotMetadata alloc] initWithPendingWrites:NO fromCache:YES]; XCTAssertEqualObjects(foo, fooDup); XCTAssertNotEqualObjects(foo, bar); XCTAssertNotEqualObjects(foo, baz); @@ -47,6 +44,16 @@ - (void)testEquals { XCTAssertNotEqual([bar hash], [baz hash]); } +- (void)testProperties { + FIRSnapshotMetadata *metadata = [[FIRSnapshotMetadata alloc] initWithPendingWrites:YES + fromCache:NO]; + XCTAssertTrue(metadata.hasPendingWrites); + XCTAssertTrue(metadata.pendingWrites); + + XCTAssertFalse(metadata.isFromCache); + XCTAssertFalse(metadata.fromCache); +} + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.h b/Firestore/Example/Tests/API/FSTAPIHelpers.h index 2325c228cec..2c5883d6609 100644 --- a/Firestore/Example/Tests/API/FSTAPIHelpers.h +++ b/Firestore/Example/Tests/API/FSTAPIHelpers.h @@ -66,8 +66,8 @@ FIRQuerySnapshot *FSTTestQuerySnapshot( const absl::string_view path, NSDictionary *> *oldDocs, NSDictionary *> *docsToAdd, - BOOL hasPendingWrites, - BOOL fromCache); + bool hasPendingWrites, + bool fromCache); #if __cplusplus } // extern "C" diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.mm b/Firestore/Example/Tests/API/FSTAPIHelpers.mm index 3e8d263f0e8..ee9f4d15cb0 100644 --- a/Firestore/Example/Tests/API/FSTAPIHelpers.mm +++ b/Firestore/Example/Tests/API/FSTAPIHelpers.mm @@ -32,9 +32,9 @@ #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" #include "Firestore/core/test/firebase/firestore/testutil/testutil.h" @@ -43,6 +43,7 @@ using firebase::firestore::core::DocumentViewChange; using firebase::firestore::core::ViewSnapshot; using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentSet; NS_ASSUME_NONNULL_BEGIN @@ -55,9 +56,9 @@ dispatch_once(&onceToken, ^{ sharedInstance = [[FIRFirestore alloc] initWithProjectID:"abc" database:"abc" - persistenceKey:@"db123" - credentialsProvider:nil - workerQueue:nil + persistenceKey:"db123" + credentialsProvider:nullptr + workerQueue:nullptr firebaseApp:nil]; }); #pragma clang diagnostic pop @@ -73,11 +74,11 @@ data ? FSTTestDoc(path, version, data, hasMutations ? FSTDocumentStateLocalMutations : FSTDocumentStateSynced) : nil; - return [FIRDocumentSnapshot snapshotWithFirestore:FSTTestFirestore() - documentKey:testutil::Key(path) - document:doc - fromCache:fromCache - hasPendingWrites:hasMutations]; + return [[FIRDocumentSnapshot alloc] initWithFirestore:FSTTestFirestore().wrapped + documentKey:testutil::Key(path) + document:doc + fromCache:fromCache + hasPendingWrites:hasMutations]; } FIRCollectionReference *FSTTestCollectionRef(const absl::string_view path) { @@ -86,8 +87,8 @@ } FIRDocumentReference *FSTTestDocRef(const absl::string_view path) { - return [FIRDocumentReference referenceWithPath:testutil::Resource(path) - firestore:FSTTestFirestore()]; + return [[FIRDocumentReference alloc] initWithPath:testutil::Resource(path) + firestore:FSTTestFirestore().wrapped]; } /** A convenience method for creating a query snapshots for tests. */ @@ -95,30 +96,27 @@ const absl::string_view path, NSDictionary *> *oldDocs, NSDictionary *> *docsToAdd, - BOOL hasPendingWrites, - BOOL fromCache) { - FIRSnapshotMetadata *metadata = - [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:hasPendingWrites fromCache:fromCache]; - FSTDocumentSet *oldDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[]); + bool hasPendingWrites, + bool fromCache) { + SnapshotMetadata metadata(hasPendingWrites, fromCache); + DocumentSet oldDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[]); DocumentKeySet mutatedKeys; for (NSString *key in oldDocs) { - oldDocuments = [oldDocuments - documentSetByAddingDocument:FSTTestDoc(util::StringFormat("%s/%s", path, key), 1, - oldDocs[key], - hasPendingWrites ? FSTDocumentStateLocalMutations - : FSTDocumentStateSynced)]; + oldDocuments = oldDocuments.insert( + FSTTestDoc(util::StringFormat("%s/%s", path, key), 1, oldDocs[key], + hasPendingWrites ? FSTDocumentStateLocalMutations : FSTDocumentStateSynced)); if (hasPendingWrites) { const std::string documentKey = util::StringFormat("%s/%s", path, key); mutatedKeys = mutatedKeys.insert(testutil::Key(documentKey)); } } - FSTDocumentSet *newDocuments = oldDocuments; + DocumentSet newDocuments = oldDocuments; std::vector documentChanges; for (NSString *key in docsToAdd) { FSTDocument *docToAdd = FSTTestDoc(util::StringFormat("%s/%s", path, key), 1, docsToAdd[key], hasPendingWrites ? FSTDocumentStateLocalMutations : FSTDocumentStateSynced); - newDocuments = [newDocuments documentSetByAddingDocument:docToAdd]; + newDocuments = newDocuments.insert(docToAdd); documentChanges.emplace_back(docToAdd, DocumentViewChange::Type::kAdded); if (hasPendingWrites) { const std::string documentKey = util::StringFormat("%s/%s", path, key); @@ -133,10 +131,10 @@ fromCache, /*sync_state_changed=*/true, /*excludes_metadata_changes=*/false}; - return [FIRQuerySnapshot snapshotWithFirestore:FSTTestFirestore() - originalQuery:FSTTestQuery(path) - snapshot:std::move(viewSnapshot) - metadata:metadata]; + return [[FIRQuerySnapshot alloc] initWithFirestore:FSTTestFirestore().wrapped + originalQuery:FSTTestQuery(path) + snapshot:std::move(viewSnapshot) + metadata:std::move(metadata)]; } NS_ASSUME_NONNULL_END diff --git a/Firestore/Example/Tests/Core/FSTEventManagerTests.mm b/Firestore/Example/Tests/Core/FSTEventManagerTests.mm index 11152bd5035..2413b00b3cd 100644 --- a/Firestore/Example/Tests/Core/FSTEventManagerTests.mm +++ b/Firestore/Example/Tests/Core/FSTEventManagerTests.mm @@ -19,37 +19,46 @@ #import #import +#include #include +#include #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Core/FSTSyncEngine.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Example/Tests/Util/FSTHelpers.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" #include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/model/types.h" #include "Firestore/core/src/firebase/firestore/util/statusor.h" +#include "Firestore/core/test/firebase/firestore/testutil/xcgmock.h" +using firebase::firestore::core::EventListener; +using firebase::firestore::core::ListenOptions; using firebase::firestore::core::ViewSnapshot; -using firebase::firestore::core::ViewSnapshotHandler; using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentSet; using firebase::firestore::model::OnlineState; using firebase::firestore::util::StatusOr; +using firebase::firestore::util::StatusOrCallback; +using testing::ElementsAre; NS_ASSUME_NONNULL_BEGIN -/** - * Converts an OnlineState to an NSNumber, usually for the purpose of adding - * it to an NSArray or similar container. There's no direct conversion from a - * strongly-typed enum to an integral type that could be passed to an NSNumber - * initializer. - */ -static NSNumber *ToNSNumber(OnlineState state) { - return @(static_cast::type>(state)); +namespace { + +ViewSnapshot::Listener NoopViewSnapshotHandler() { + return EventListener::Create([](const StatusOr &) {}); +} + +std::shared_ptr NoopQueryListener(FSTQuery *query) { + return QueryListener::Create(query, ListenOptions::DefaultOptions(), NoopViewSnapshotHandler()); } +} // namespace + // FSTEventManager implements this delegate privately @interface FSTEventManager () @end @@ -59,16 +68,10 @@ @interface FSTEventManagerTests : XCTestCase @implementation FSTEventManagerTests -- (FSTQueryListener *)noopListenerForQuery:(FSTQuery *)query { - return [[FSTQueryListener alloc] initWithQuery:query - options:[FSTListenOptions defaultOptions] - viewSnapshotHandler:[](const StatusOr &) {}]; -} - - (void)testHandlesManyListenersPerQuery { FSTQuery *query = FSTTestQuery("foo/bar"); - FSTQueryListener *listener1 = [self noopListenerForQuery:query]; - FSTQueryListener *listener2 = [self noopListenerForQuery:query]; + auto listener1 = NoopQueryListener(query); + auto listener2 = NoopQueryListener(query); FSTSyncEngine *syncEngineMock = OCMStrictClassMock([FSTSyncEngine class]); OCMExpect([syncEngineMock setSyncEngineDelegate:[OCMArg any]]); @@ -88,7 +91,7 @@ - (void)testHandlesManyListenersPerQuery { - (void)testHandlesUnlistenOnUnknownListenerGracefully { FSTQuery *query = FSTTestQuery("foo/bar"); - FSTQueryListener *listener = [self noopListenerForQuery:query]; + auto listener = NoopQueryListener(query); FSTSyncEngine *syncEngineMock = OCMStrictClassMock([FSTSyncEngine class]); OCMExpect([syncEngineMock setSyncEngineDelegate:[OCMArg any]]); @@ -98,15 +101,8 @@ - (void)testHandlesUnlistenOnUnknownListenerGracefully { OCMVerifyAll((id)syncEngineMock); } -- (FSTQueryListener *)queryListenerForQuery:(FSTQuery *)query - withHandler:(ViewSnapshotHandler &&)handler { - return [[FSTQueryListener alloc] initWithQuery:query - options:[FSTListenOptions defaultOptions] - viewSnapshotHandler:std::move(handler)]; -} - - (ViewSnapshot)makeEmptyViewSnapshotWithQuery:(FSTQuery *)query { - FSTDocumentSet *emptyDocs = [FSTDocumentSet documentSetWithComparator:query.comparator]; + DocumentSet emptyDocs{query.comparator}; // sync_state_changed has to be `true` to prevent an assertion about a meaningless view snapshot. return ViewSnapshot{ query, emptyDocs, emptyDocs, {}, DocumentKeySet{}, false, /*sync_state_changed=*/true, false}; @@ -117,23 +113,14 @@ - (void)testNotifiesListenersInTheRightOrder { FSTQuery *query2 = FSTTestQuery("bar/baz"); NSMutableArray *eventOrder = [NSMutableArray array]; - FSTQueryListener *listener1 = - [self queryListenerForQuery:query1 - withHandler:[eventOrder](const StatusOr &) { - [eventOrder addObject:@"listener1"]; - }]; + auto listener1 = QueryListener::Create( + query1, [eventOrder](StatusOr) { [eventOrder addObject:@"listener1"]; }); - FSTQueryListener *listener2 = - [self queryListenerForQuery:query2 - withHandler:[eventOrder](const StatusOr &) { - [eventOrder addObject:@"listener2"]; - }]; + auto listener2 = QueryListener::Create( + query2, [eventOrder](StatusOr) { [eventOrder addObject:@"listener2"]; }); - FSTQueryListener *listener3 = - [self queryListenerForQuery:query1 - withHandler:[eventOrder](const StatusOr &) { - [eventOrder addObject:@"listener3"]; - }]; + auto listener3 = QueryListener::Create( + query1, [eventOrder](StatusOr) { [eventOrder addObject:@"listener3"]; }); FSTSyncEngine *syncEngineMock = OCMClassMock([FSTSyncEngine class]); FSTEventManager *eventManager = [FSTEventManager eventManagerWithSyncEngine:syncEngineMock]; @@ -154,27 +141,31 @@ - (void)testNotifiesListenersInTheRightOrder { - (void)testWillForwardOnlineStateChanges { FSTQuery *query = FSTTestQuery("foo/bar"); - FSTQueryListener *fakeListener = OCMClassMock([FSTQueryListener class]); - NSMutableArray *events = [NSMutableArray array]; - OCMStub([fakeListener query]).andReturn(query); - OCMStub([fakeListener applyChangedOnlineState:OnlineState::Unknown]) - .andDo(^(NSInvocation *invocation) { - [events addObject:ToNSNumber(OnlineState::Unknown)]; - }); - OCMStub([fakeListener applyChangedOnlineState:OnlineState::Online]) - .andDo(^(NSInvocation *invocation) { - [events addObject:ToNSNumber(OnlineState::Online)]; - }); + + class FakeQueryListener : public QueryListener { + public: + explicit FakeQueryListener(FSTQuery *query) + : QueryListener(query, ListenOptions::DefaultOptions(), NoopViewSnapshotHandler()) { + } + + void OnOnlineStateChanged(OnlineState online_state) override { + events.push_back(online_state); + } + + std::vector events; + }; + + auto fake_listener = std::make_shared(query); FSTSyncEngine *syncEngineMock = OCMClassMock([FSTSyncEngine class]); OCMExpect([syncEngineMock setSyncEngineDelegate:[OCMArg any]]); FSTEventManager *eventManager = [FSTEventManager eventManagerWithSyncEngine:syncEngineMock]; - [eventManager addListener:fakeListener]; - XCTAssertEqualObjects(events, @[ ToNSNumber(OnlineState::Unknown) ]); + [eventManager addListener:fake_listener]; + XC_ASSERT_THAT(fake_listener->events, ElementsAre(OnlineState::Unknown)); + [eventManager applyChangedOnlineState:OnlineState::Online]; - XCTAssertEqualObjects(events, - (@[ ToNSNumber(OnlineState::Unknown), ToNSNumber(OnlineState::Online) ])); + XC_ASSERT_THAT(fake_listener->events, ElementsAre(OnlineState::Unknown, OnlineState::Online)); } @end diff --git a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm index f4b700c17eb..04ee1bb67a8 100644 --- a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm +++ b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm @@ -24,53 +24,44 @@ #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Core/FSTView.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" -#import "Firestore/Source/Util/FSTAsyncQueryListener.h" #import "Firestore/Example/Tests/Util/FSTHelpers.h" #include "Firestore/core/include/firebase/firestore/firestore_errors.h" +#include "Firestore/core/src/firebase/firestore/core/event_listener.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/model/types.h" #include "Firestore/core/src/firebase/firestore/remote/remote_event.h" +#include "Firestore/core/src/firebase/firestore/util/delayed_constructor.h" #include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h" #include "Firestore/core/src/firebase/firestore/util/status.h" #include "Firestore/core/src/firebase/firestore/util/statusor.h" -#include "absl/memory/memory.h" +#include "Firestore/core/test/firebase/firestore/testutil/xcgmock.h" using firebase::firestore::FirestoreErrorCode; +using firebase::firestore::core::AsyncEventListener; +using firebase::firestore::core::EventListener; using firebase::firestore::core::DocumentViewChange; +using firebase::firestore::core::EventListener; +using firebase::firestore::core::ListenOptions; using firebase::firestore::core::ViewSnapshot; -using firebase::firestore::core::ViewSnapshotHandler; using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentSet; using firebase::firestore::model::OnlineState; using firebase::firestore::remote::TargetChange; +using firebase::firestore::util::DelayedConstructor; using firebase::firestore::util::ExecutorLibdispatch; using firebase::firestore::util::Status; using firebase::firestore::util::StatusOr; +using testing::ElementsAre; +using testing::IsEmpty; NS_ASSUME_NONNULL_BEGIN -@interface FSTQueryListenerTests : XCTestCase -@end - -@implementation FSTQueryListenerTests { - std::unique_ptr _executor; - FSTListenOptions *_includeMetadataChanges; -} - -- (void)setUp { - // TODO(varconst): moving this test to C++, it should be possible to store Executor as a value, - // not a pointer, and initialize it in the constructor. - _executor = absl::make_unique( - dispatch_queue_create("FSTQueryListenerTests Queue", DISPATCH_QUEUE_SERIAL)); - _includeMetadataChanges = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES - includeDocumentMetadataChanges:YES - waitForSyncWhenOnline:NO]; -} +namespace { -- (ViewSnapshot)setExcludesMetadataChanges:(BOOL)excludesMetadataChanges - snapshot:(const ViewSnapshot &)snapshot { +ViewSnapshot ExcludingMetadataChanges(const ViewSnapshot &snapshot) { return ViewSnapshot{ snapshot.query(), snapshot.documents(), @@ -79,10 +70,32 @@ - (ViewSnapshot)setExcludesMetadataChanges:(BOOL)excludesMetadataChanges snapshot.mutated_keys(), snapshot.from_cache(), snapshot.sync_state_changed(), - excludesMetadataChanges, + /*excludes_metadata_changes=*/true, }; } +ViewSnapshot::Listener Accumulating(std::vector *values) { + return EventListener::Create( + [values](const StatusOr &maybe_snapshot) { + values->push_back(maybe_snapshot.ValueOrDie()); + }); +} + +} // namespace + +@interface FSTQueryListenerTests : XCTestCase +@end + +@implementation FSTQueryListenerTests { + DelayedConstructor _executor; + ListenOptions _includeMetadataChanges; +} + +- (void)setUp { + _executor.Init(dispatch_queue_create("FSTQueryListenerTests Queue", DISPATCH_QUEUE_SERIAL)); + _includeMetadataChanges = ListenOptions::FromIncludeMetadataChanges(true); +} + - (void)testRaisesCollectionEvents { std::vector accum; std::vector otherAccum; @@ -93,10 +106,8 @@ - (void)testRaisesCollectionEvents { FSTDocument *doc2prime = FSTTestDoc("rooms/Hades", 3, @{@"name" : @"Hades", @"owner" : @"Jonny"}, FSTDocumentStateSynced); - FSTQueryListener *listener = [self listenToQuery:query - options:_includeMetadataChanges - accumulatingSnapshots:&accum]; - FSTQueryListener *otherListener = [self listenToQuery:query accumulatingSnapshots:&otherAccum]; + auto listener = QueryListener::Create(query, _includeMetadataChanges, Accumulating(&accum)); + auto otherListener = QueryListener::Create(query, Accumulating(&otherAccum)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[ doc1, doc2 ], absl::nullopt).value(); @@ -107,81 +118,77 @@ - (void)testRaisesCollectionEvents { DocumentViewChange change3{doc2prime, DocumentViewChange::Type::kModified}; DocumentViewChange change4{doc2prime, DocumentViewChange::Type::kAdded}; - [listener queryDidChangeViewSnapshot:snap1]; - [listener queryDidChangeViewSnapshot:snap2]; - [otherListener queryDidChangeViewSnapshot:snap2]; - - XCTAssertTrue((accum == std::vector{snap1, snap2})); - XCTAssertTrue((accum[0].document_changes() == std::vector{change1, change2})); - XCTAssertTrue(accum[1].document_changes() == std::vector{change3}); - - ViewSnapshot expectedSnap2{ - snap2.query(), - snap2.documents(), - /*old_documents=*/[FSTDocumentSet documentSetWithComparator : snap2.query().comparator], - /*document_changes=*/{ change1, change4 }, - snap2.mutated_keys(), - snap2.from_cache(), - /*sync_state_changed=*/true, - /*excludes_metadata_changes=*/true}; - XCTAssertTrue((otherAccum == std::vector{expectedSnap2})); + listener->OnViewSnapshot(snap1); + listener->OnViewSnapshot(snap2); + otherListener->OnViewSnapshot(snap2); + + XC_ASSERT_THAT(accum, ElementsAre(snap1, snap2)); + XC_ASSERT_THAT(accum[0].document_changes(), ElementsAre(change1, change2)); + XC_ASSERT_THAT(accum[1].document_changes(), ElementsAre(change3)); + + ViewSnapshot expectedSnap2{snap2.query(), + snap2.documents(), + /*old_documents=*/DocumentSet{snap2.query().comparator}, + /*document_changes=*/{change1, change4}, + snap2.mutated_keys(), + snap2.from_cache(), + /*sync_state_changed=*/true, + /*excludes_metadata_changes=*/true}; + XC_ASSERT_THAT(otherAccum, ElementsAre(expectedSnap2)); } - (void)testRaisesErrorEvent { __block std::vector accum; FSTQuery *query = FSTTestQuery("rooms/Eros"); - FSTQueryListener *listener = [self listenToQuery:query - handler:^(const StatusOr &maybe_snapshot) { - accum.push_back(maybe_snapshot.status()); - }]; + auto listener = QueryListener::Create(query, ^(const StatusOr &maybe_snapshot) { + accum.push_back(maybe_snapshot.status()); + }); Status testError{FirestoreErrorCode::Unauthenticated, "Some info"}; - [listener queryDidError:testError]; + listener->OnError(testError); - XCTAssertTrue((accum == std::vector{testError})); + XC_ASSERT_THAT(accum, ElementsAre(testError)); } - (void)testRaisesEventForEmptyCollectionAfterSync { std::vector accum; FSTQuery *query = FSTTestQuery("rooms"); - FSTQueryListener *listener = [self listenToQuery:query - options:_includeMetadataChanges - accumulatingSnapshots:&accum]; + auto listener = QueryListener::Create(query, _includeMetadataChanges, Accumulating(&accum)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[], absl::nullopt).value(); ViewSnapshot snap2 = FSTTestApplyChanges(view, @[], FSTTestTargetChangeMarkCurrent()).value(); - [listener queryDidChangeViewSnapshot:snap1]; - XCTAssertTrue(accum.empty()); + listener->OnViewSnapshot(snap1); + XC_ASSERT_THAT(accum, IsEmpty()); - [listener queryDidChangeViewSnapshot:snap2]; - XCTAssertTrue((accum == std::vector{snap2})); + listener->OnViewSnapshot(snap2); + XC_ASSERT_THAT(accum, ElementsAre(snap2)); } - (void)testMutingAsyncListenerPreventsAllSubsequentEvents { - __block std::vector accum; + std::vector accum; FSTQuery *query = FSTTestQuery("rooms/Eros"); FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 3, @{@"name" : @"Eros"}, FSTDocumentStateSynced); FSTDocument *doc2 = FSTTestDoc("rooms/Eros", 4, @{@"name" : @"Eros2"}, FSTDocumentStateSynced); - __block FSTAsyncQueryListener *listener = [[FSTAsyncQueryListener alloc] - initWithExecutor:_executor.get() - snapshotHandler:^(const StatusOr &maybe_snapshot) { - accum.push_back(maybe_snapshot.ValueOrDie()); - [listener mute]; - }]; + std::shared_ptr> listener = + AsyncEventListener::Create( + _executor.get(), EventListener::Create( + [&accum, &listener](const StatusOr &maybe_snapshot) { + accum.push_back(maybe_snapshot.ValueOrDie()); + listener->Mute(); + })); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot viewSnapshot1 = FSTTestApplyChanges(view, @[ doc1 ], absl::nullopt).value(); ViewSnapshot viewSnapshot2 = FSTTestApplyChanges(view, @[ doc2 ], absl::nullopt).value(); - ViewSnapshotHandler handler = listener.asyncSnapshotHandler; - handler(viewSnapshot1); - handler(viewSnapshot2); + listener->OnEvent(viewSnapshot1); + listener->OnEvent(viewSnapshot2); // Drain queue XCTestExpectation *expectation = [self expectationWithDescription:@"Queue drained"]; @@ -195,7 +202,7 @@ - (void)testMutingAsyncListenerPreventsAllSubsequentEvents { }]; // We should get the first snapshot but not the second. - XCTAssertTrue((accum == std::vector{viewSnapshot1})); + XC_ASSERT_THAT(accum, ElementsAre(viewSnapshot1)); } - (void)testDoesNotRaiseEventsForMetadataChangesUnlessSpecified { @@ -206,11 +213,9 @@ - (void)testDoesNotRaiseEventsForMetadataChangesUnlessSpecified { FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); - FSTQueryListener *filteredListener = [self listenToQuery:query - accumulatingSnapshots:&filteredAccum]; - FSTQueryListener *fullListener = [self listenToQuery:query - options:_includeMetadataChanges - accumulatingSnapshots:&fullAccum]; + auto filteredListener = QueryListener::Create(query, Accumulating(&filteredAccum)); + auto fullListener = + QueryListener::Create(query, _includeMetadataChanges, Accumulating(&fullAccum)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[ doc1 ], absl::nullopt).value(); @@ -219,18 +224,17 @@ - (void)testDoesNotRaiseEventsForMetadataChangesUnlessSpecified { ViewSnapshot snap2 = FSTTestApplyChanges(view, @[], ackTarget).value(); ViewSnapshot snap3 = FSTTestApplyChanges(view, @[ doc2 ], absl::nullopt).value(); - [filteredListener queryDidChangeViewSnapshot:snap1]; // local event - [filteredListener queryDidChangeViewSnapshot:snap2]; // no event - [filteredListener queryDidChangeViewSnapshot:snap3]; // doc2 update + filteredListener->OnViewSnapshot(snap1); // local event + filteredListener->OnViewSnapshot(snap2); // no event + filteredListener->OnViewSnapshot(snap3); // doc2 update - [fullListener queryDidChangeViewSnapshot:snap1]; // local event - [fullListener queryDidChangeViewSnapshot:snap2]; // state change event - [fullListener queryDidChangeViewSnapshot:snap3]; // doc2 update + fullListener->OnViewSnapshot(snap1); // local event + fullListener->OnViewSnapshot(snap2); // state change event + fullListener->OnViewSnapshot(snap3); // doc2 update - XCTAssertTrue((filteredAccum == - std::vector{[self setExcludesMetadataChanges:YES snapshot:snap1], - [self setExcludesMetadataChanges:YES snapshot:snap3]})); - XCTAssertTrue((fullAccum == std::vector{snap1, snap2, snap3})); + XC_ASSERT_THAT(filteredAccum, + ElementsAre(ExcludingMetadataChanges(snap1), ExcludingMetadataChanges(snap3))); + XC_ASSERT_THAT(fullAccum, ElementsAre(snap1, snap2, snap3)); } - (void)testRaisesDocumentMetadataEventsOnlyWhenSpecified { @@ -245,15 +249,13 @@ - (void)testRaisesDocumentMetadataEventsOnlyWhenSpecified { FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, FSTDocumentStateSynced); - FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO - includeDocumentMetadataChanges:YES - waitForSyncWhenOnline:NO]; + ListenOptions options( + /*include_query_metadata_changes=*/false, + /*include_document_metadata_changes=*/true, + /*wait_for_sync_when_online=*/false); - FSTQueryListener *filteredListener = [self listenToQuery:query - accumulatingSnapshots:&filteredAccum]; - FSTQueryListener *fullListener = [self listenToQuery:query - options:options - accumulatingSnapshots:&fullAccum]; + auto filteredListener = QueryListener::Create(query, Accumulating(&filteredAccum)); + auto fullListener = QueryListener::Create(query, options, Accumulating(&fullAccum)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[ doc1, doc2 ], absl::nullopt).value(); @@ -265,25 +267,22 @@ - (void)testRaisesDocumentMetadataEventsOnlyWhenSpecified { DocumentViewChange change3{doc1Prime, DocumentViewChange::Type::kMetadata}; DocumentViewChange change4{doc3, DocumentViewChange::Type::kAdded}; - [filteredListener queryDidChangeViewSnapshot:snap1]; - [filteredListener queryDidChangeViewSnapshot:snap2]; - [filteredListener queryDidChangeViewSnapshot:snap3]; - [fullListener queryDidChangeViewSnapshot:snap1]; - [fullListener queryDidChangeViewSnapshot:snap2]; - [fullListener queryDidChangeViewSnapshot:snap3]; - - XCTAssertTrue((filteredAccum == - std::vector{[self setExcludesMetadataChanges:YES snapshot:snap1], - [self setExcludesMetadataChanges:YES snapshot:snap3]})); - XCTAssertTrue( - (filteredAccum[0].document_changes() == std::vector{change1, change2})); - XCTAssertTrue((filteredAccum[1].document_changes() == std::vector{change4})); - - XCTAssertTrue((fullAccum == std::vector{snap1, snap2, snap3})); - XCTAssertTrue( - (fullAccum[0].document_changes() == std::vector{change1, change2})); - XCTAssertTrue((fullAccum[1].document_changes() == std::vector{change3})); - XCTAssertTrue((fullAccum[2].document_changes() == std::vector{change4})); + filteredListener->OnViewSnapshot(snap1); + filteredListener->OnViewSnapshot(snap2); + filteredListener->OnViewSnapshot(snap3); + fullListener->OnViewSnapshot(snap1); + fullListener->OnViewSnapshot(snap2); + fullListener->OnViewSnapshot(snap3); + + XC_ASSERT_THAT(filteredAccum, + ElementsAre(ExcludingMetadataChanges(snap1), ExcludingMetadataChanges(snap3))); + XC_ASSERT_THAT(filteredAccum[0].document_changes(), ElementsAre(change1, change2)); + XC_ASSERT_THAT(filteredAccum[1].document_changes(), ElementsAre(change4)); + + XC_ASSERT_THAT(fullAccum, ElementsAre(snap1, snap2, snap3)); + XC_ASSERT_THAT(fullAccum[0].document_changes(), ElementsAre(change1, change2)); + XC_ASSERT_THAT(fullAccum[1].document_changes(), ElementsAre(change3)); + XC_ASSERT_THAT(fullAccum[2].document_changes(), ElementsAre(change4)); } - (void)testRaisesQueryMetadataEventsOnlyWhenHasPendingWritesOnTheQueryChanges { @@ -300,12 +299,11 @@ - (void)testRaisesQueryMetadataEventsOnlyWhenHasPendingWritesOnTheQueryChanges { FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, FSTDocumentStateSynced); - FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES - includeDocumentMetadataChanges:NO - waitForSyncWhenOnline:NO]; - FSTQueryListener *fullListener = [self listenToQuery:query - options:options - accumulatingSnapshots:&fullAccum]; + ListenOptions options( + /*include_query_metadata_changes=*/true, + /*include_document_metadata_changes=*/false, + /*wait_for_sync_when_online=*/false); + auto fullListener = QueryListener::Create(query, options, Accumulating(&fullAccum)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[ doc1, doc2 ], absl::nullopt).value(); @@ -313,10 +311,10 @@ - (void)testRaisesQueryMetadataEventsOnlyWhenHasPendingWritesOnTheQueryChanges { ViewSnapshot snap3 = FSTTestApplyChanges(view, @[ doc3 ], absl::nullopt).value(); ViewSnapshot snap4 = FSTTestApplyChanges(view, @[ doc2Prime ], absl::nullopt).value(); - [fullListener queryDidChangeViewSnapshot:snap1]; - [fullListener queryDidChangeViewSnapshot:snap2]; // Emits no events. - [fullListener queryDidChangeViewSnapshot:snap3]; - [fullListener queryDidChangeViewSnapshot:snap4]; // Metadata change event. + fullListener->OnViewSnapshot(snap1); + fullListener->OnViewSnapshot(snap2); // Emits no events. + fullListener->OnViewSnapshot(snap3); + fullListener->OnViewSnapshot(snap4); // Metadata change event. ViewSnapshot expectedSnap4{ snap4.query(), @@ -328,10 +326,9 @@ - (void)testRaisesQueryMetadataEventsOnlyWhenHasPendingWritesOnTheQueryChanges { snap4.sync_state_changed(), /*excludes_metadata_changes=*/true // This test excludes document metadata changes }; - XCTAssertTrue( - (fullAccum == std::vector{[self setExcludesMetadataChanges:YES snapshot:snap1], - [self setExcludesMetadataChanges:YES snapshot:snap3], - expectedSnap4})); + + XC_ASSERT_THAT(fullAccum, ElementsAre(ExcludingMetadataChanges(snap1), + ExcludingMetadataChanges(snap3), expectedSnap4)); } - (void)testMetadataOnlyDocumentChangesAreFilteredOutWhenIncludeDocumentMetadataChangesIsFalse { @@ -345,8 +342,7 @@ - (void)testMetadataOnlyDocumentChangesAreFilteredOutWhenIncludeDocumentMetadata FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, FSTDocumentStateSynced); - FSTQueryListener *filteredListener = [self listenToQuery:query - accumulatingSnapshots:&filteredAccum]; + auto filteredListener = QueryListener::Create(query, Accumulating(&filteredAccum)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[ doc1, doc2 ], absl::nullopt).value(); @@ -354,8 +350,8 @@ - (void)testMetadataOnlyDocumentChangesAreFilteredOutWhenIncludeDocumentMetadata DocumentViewChange change3{doc3, DocumentViewChange::Type::kAdded}; - [filteredListener queryDidChangeViewSnapshot:snap1]; - [filteredListener queryDidChangeViewSnapshot:snap2]; + filteredListener->OnViewSnapshot(snap1); + filteredListener->OnViewSnapshot(snap2); ViewSnapshot expectedSnap2{snap2.query(), snap2.documents(), @@ -365,9 +361,7 @@ - (void)testMetadataOnlyDocumentChangesAreFilteredOutWhenIncludeDocumentMetadata snap2.from_cache(), snap2.sync_state_changed(), /*excludes_metadata_changes=*/true}; - XCTAssertTrue((filteredAccum == std::vector{[self setExcludesMetadataChanges:YES - snapshot:snap1], - expectedSnap2})); + XC_ASSERT_THAT(filteredAccum, ElementsAre(ExcludingMetadataChanges(snap1), expectedSnap2)); } - (void)testWillWaitForSyncIfOnline { @@ -376,12 +370,12 @@ - (void)testWillWaitForSyncIfOnline { FSTQuery *query = FSTTestQuery("rooms"); FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); - FSTQueryListener *listener = - [self listenToQuery:query - options:[[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO - includeDocumentMetadataChanges:NO - waitForSyncWhenOnline:YES] - accumulatingSnapshots:&events]; + + ListenOptions options( + /*include_query_metadata_changes=*/false, + /*include_document_metadata_changes=*/false, + /*wait_for_sync_when_online=*/true); + auto listener = QueryListener::Create(query, options, Accumulating(&events)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[ doc1 ], absl::nullopt).value(); @@ -389,25 +383,24 @@ - (void)testWillWaitForSyncIfOnline { ViewSnapshot snap3 = FSTTestApplyChanges(view, @[], FSTTestTargetChangeAckDocuments({doc1.key, doc2.key})).value(); - [listener applyChangedOnlineState:OnlineState::Online]; // no event - [listener queryDidChangeViewSnapshot:snap1]; - [listener applyChangedOnlineState:OnlineState::Unknown]; - [listener applyChangedOnlineState:OnlineState::Online]; - [listener queryDidChangeViewSnapshot:snap2]; - [listener queryDidChangeViewSnapshot:snap3]; + listener->OnOnlineStateChanged(OnlineState::Online); // no event + listener->OnViewSnapshot(snap1); + listener->OnOnlineStateChanged(OnlineState::Unknown); + listener->OnOnlineStateChanged(OnlineState::Online); + listener->OnViewSnapshot(snap2); + listener->OnViewSnapshot(snap3); DocumentViewChange change1{doc1, DocumentViewChange::Type::kAdded}; DocumentViewChange change2{doc2, DocumentViewChange::Type::kAdded}; - ViewSnapshot expectedSnap{ - snap3.query(), - snap3.documents(), - /*old_documents=*/[FSTDocumentSet documentSetWithComparator : snap3.query().comparator], - /*document_changes=*/{ change1, change2 }, - snap3.mutated_keys(), - /*from_cache=*/false, - /*sync_state_changed=*/true, - /*excludes_metadata_changes=*/true}; - XCTAssertTrue((events == std::vector{expectedSnap})); + ViewSnapshot expectedSnap{snap3.query(), + snap3.documents(), + /*old_documents=*/DocumentSet{snap3.query().comparator}, + /*document_changes=*/{change1, change2}, + snap3.mutated_keys(), + /*from_cache=*/false, + /*sync_state_changed=*/true, + /*excludes_metadata_changes=*/true}; + XC_ASSERT_THAT(events, ElementsAre(expectedSnap)); } - (void)testWillRaiseInitialEventWhenGoingOffline { @@ -416,35 +409,35 @@ - (void)testWillRaiseInitialEventWhenGoingOffline { FSTQuery *query = FSTTestQuery("rooms"); FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); - FSTQueryListener *listener = - [self listenToQuery:query - options:[[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO - includeDocumentMetadataChanges:NO - waitForSyncWhenOnline:YES] - accumulatingSnapshots:&events]; + + ListenOptions options( + /*include_query_metadata_changes=*/false, + /*include_document_metadata_changes=*/false, + /*wait_for_sync_when_online=*/true); + + auto listener = QueryListener::Create(query, options, Accumulating(&events)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[ doc1 ], absl::nullopt).value(); ViewSnapshot snap2 = FSTTestApplyChanges(view, @[ doc2 ], absl::nullopt).value(); - [listener applyChangedOnlineState:OnlineState::Online]; // no event - [listener queryDidChangeViewSnapshot:snap1]; // no event - [listener applyChangedOnlineState:OnlineState::Offline]; // event - [listener applyChangedOnlineState:OnlineState::Unknown]; // no event - [listener applyChangedOnlineState:OnlineState::Offline]; // no event - [listener queryDidChangeViewSnapshot:snap2]; // another event + listener->OnOnlineStateChanged(OnlineState::Online); // no event + listener->OnViewSnapshot(snap1); // no event + listener->OnOnlineStateChanged(OnlineState::Offline); // event + listener->OnOnlineStateChanged(OnlineState::Unknown); // no event + listener->OnOnlineStateChanged(OnlineState::Offline); // no event + listener->OnViewSnapshot(snap2); // another event DocumentViewChange change1{doc1, DocumentViewChange::Type::kAdded}; DocumentViewChange change2{doc2, DocumentViewChange::Type::kAdded}; - ViewSnapshot expectedSnap1{ - query, - /*documents=*/snap1.documents(), - /*old_documents=*/[FSTDocumentSet documentSetWithComparator : snap1.query().comparator], - /*document_changes=*/{ change1 }, - snap1.mutated_keys(), - /*from_cache=*/true, - /*sync_state_changed=*/true, - /*excludes_metadata_changes=*/true}; + ViewSnapshot expectedSnap1{query, + /*documents=*/snap1.documents(), + /*old_documents=*/DocumentSet{snap1.query().comparator}, + /*document_changes=*/{change1}, + snap1.mutated_keys(), + /*from_cache=*/true, + /*sync_state_changed=*/true, + /*excludes_metadata_changes=*/true}; ViewSnapshot expectedSnap2{query, /*documents=*/snap2.documents(), @@ -454,83 +447,54 @@ - (void)testWillRaiseInitialEventWhenGoingOffline { /*from_cache=*/true, /*sync_state_changed=*/false, /*excludes_metadata_changes=*/true}; - XCTAssertTrue((events == std::vector{expectedSnap1, expectedSnap2})); + XC_ASSERT_THAT(events, ElementsAre(expectedSnap1, expectedSnap2)); } - (void)testWillRaiseInitialEventWhenGoingOfflineAndThereAreNoDocs { std::vector events; FSTQuery *query = FSTTestQuery("rooms"); - FSTQueryListener *listener = [self listenToQuery:query - options:[FSTListenOptions defaultOptions] - accumulatingSnapshots:&events]; + auto listener = QueryListener::Create(query, Accumulating(&events)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[], absl::nullopt).value(); - [listener applyChangedOnlineState:OnlineState::Online]; // no event - [listener queryDidChangeViewSnapshot:snap1]; // no event - [listener applyChangedOnlineState:OnlineState::Offline]; // event - - ViewSnapshot expectedSnap{ - query, - /*documents=*/snap1.documents(), - /*old_documents=*/[FSTDocumentSet documentSetWithComparator : snap1.query().comparator], - /*document_changes=*/{}, - snap1.mutated_keys(), - /*from_cache=*/true, - /*sync_state_changed=*/true, - /*excludes_metadata_changes=*/true}; - XCTAssertTrue((events == std::vector{expectedSnap})); + listener->OnOnlineStateChanged(OnlineState::Online); // no event + listener->OnViewSnapshot(snap1); // no event + listener->OnOnlineStateChanged(OnlineState::Offline); // event + + ViewSnapshot expectedSnap{query, + /*documents=*/snap1.documents(), + /*old_documents=*/DocumentSet{snap1.query().comparator}, + /*document_changes=*/{}, + snap1.mutated_keys(), + /*from_cache=*/true, + /*sync_state_changed=*/true, + /*excludes_metadata_changes=*/true}; + XC_ASSERT_THAT(events, ElementsAre(expectedSnap)); } - (void)testWillRaiseInitialEventWhenStartingOfflineAndThereAreNoDocs { std::vector events; FSTQuery *query = FSTTestQuery("rooms"); - FSTQueryListener *listener = [self listenToQuery:query - options:[FSTListenOptions defaultOptions] - accumulatingSnapshots:&events]; + auto listener = QueryListener::Create(query, Accumulating(&events)); FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; ViewSnapshot snap1 = FSTTestApplyChanges(view, @[], absl::nullopt).value(); - [listener applyChangedOnlineState:OnlineState::Offline]; // no event - [listener queryDidChangeViewSnapshot:snap1]; // event - - ViewSnapshot expectedSnap{ - query, - /*documents=*/snap1.documents(), - /*old_documents=*/[FSTDocumentSet documentSetWithComparator : snap1.query().comparator], - /*document_changes=*/{}, - snap1.mutated_keys(), - /*from_cache=*/true, - /*sync_state_changed=*/true, - /*excludes_metadata_changes=*/true}; - XCTAssertTrue((events == std::vector{expectedSnap})); -} - -- (FSTQueryListener *)listenToQuery:(FSTQuery *)query handler:(ViewSnapshotHandler &&)handler { - return [[FSTQueryListener alloc] initWithQuery:query - options:[FSTListenOptions defaultOptions] - viewSnapshotHandler:std::move(handler)]; -} - -- (FSTQueryListener *)listenToQuery:(FSTQuery *)query - options:(FSTListenOptions *)options - accumulatingSnapshots:(std::vector *)values { - return [[FSTQueryListener alloc] initWithQuery:query - options:options - viewSnapshotHandler:^(const StatusOr &maybe_snapshot) { - values->push_back(maybe_snapshot.ValueOrDie()); - }]; -} - -- (FSTQueryListener *)listenToQuery:(FSTQuery *)query - accumulatingSnapshots:(std::vector *)values { - return [self listenToQuery:query - options:[FSTListenOptions defaultOptions] - accumulatingSnapshots:values]; + listener->OnOnlineStateChanged(OnlineState::Offline); // no event + listener->OnViewSnapshot(snap1); // event + + ViewSnapshot expectedSnap{query, + /*documents=*/snap1.documents(), + /*old_documents=*/DocumentSet{snap1.query().comparator}, + /*document_changes=*/{}, + snap1.mutated_keys(), + /*from_cache=*/true, + /*sync_state_changed=*/true, + /*excludes_metadata_changes=*/true}; + XC_ASSERT_THAT(events, ElementsAre(expectedSnap)); } @end diff --git a/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm b/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm index 7b312ee51dd..bd13b0ccccb 100644 --- a/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm +++ b/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm @@ -20,16 +20,17 @@ #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Example/Tests/Util/FSTHelpers.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" using firebase::firestore::core::DocumentViewChange; using firebase::firestore::core::DocumentViewChangeSet; using firebase::firestore::core::ViewSnapshot; using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentSet; NS_ASSUME_NONNULL_BEGIN @@ -100,16 +101,15 @@ - (void)testTrack { - (void)testViewSnapshotConstructor { FSTQuery *query = FSTTestQuery("a"); - FSTDocumentSet *documents = [FSTDocumentSet documentSetWithComparator:FSTDocumentComparatorByKey]; - FSTDocumentSet *oldDocuments = documents; - documents = - [documents documentSetByAddingDocument:FSTTestDoc("c/a", 1, @{}, FSTDocumentStateSynced)]; + DocumentSet documents = DocumentSet{FSTDocumentComparatorByKey}; + DocumentSet oldDocuments = documents; + documents = documents.insert(FSTTestDoc("c/a", 1, @{}, FSTDocumentStateSynced)); std::vector documentChanges{DocumentViewChange{ FSTTestDoc("c/a", 1, @{}, FSTDocumentStateSynced), DocumentViewChange::Type::kAdded}}; - BOOL fromCache = YES; + bool fromCache = true; DocumentKeySet mutatedKeys; - BOOL syncStateChanged = YES; + bool syncStateChanged = true; ViewSnapshot snapshot{query, documents, diff --git a/Firestore/Example/Tests/Core/FSTViewTests.mm b/Firestore/Example/Tests/Core/FSTViewTests.mm index 2d451a2c6ed..1c28d926e93 100644 --- a/Firestore/Example/Tests/Core/FSTViewTests.mm +++ b/Firestore/Example/Tests/Core/FSTViewTests.mm @@ -18,30 +18,58 @@ #import +#include #include #include #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Source/Model/FSTFieldValue.h" #import "Firestore/Example/Tests/Util/FSTHelpers.h" +#include "Firestore/core/src/firebase/firestore/core/filter.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/test/firebase/firestore/testutil/testutil.h" +#include "Firestore/core/test/firebase/firestore/testutil/xcgmock.h" #include "absl/types/optional.h" namespace testutil = firebase::firestore::testutil; using firebase::firestore::core::DocumentViewChange; +using firebase::firestore::core::Filter; using firebase::firestore::core::ViewSnapshot; using firebase::firestore::model::ResourcePath; using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentSet; +using testing::ElementsAre; NS_ASSUME_NONNULL_BEGIN +/** + * A custom matcher that verifies that the subject has the same keys as the given documents without + * verifying that the contents are the same. + */ +MATCHER_P(ContainsDocs, expected, "") { + if (expected.size() != arg.size()) { + return false; + } + for (FSTDocument *doc : expected) { + if (!arg.ContainsKey(doc.key)) { + return false; + } + } + return true; +} + +/** Constructs `ContainsDocs` instances with an initializer list. */ +inline ContainsDocsMatcherP> ContainsDocs( + std::vector docs) { + return ContainsDocsMatcherP>(docs); +} + @interface FSTViewTests : XCTestCase @end @@ -70,7 +98,7 @@ - (void)testAddsDocumentsBasedOnQuery { XCTAssertEqual(snapshot.query(), query); - XCTAssertEqualObjects(snapshot.documents().arrayValue, (@[ doc1, doc2 ])); + XC_ASSERT_THAT(snapshot.documents(), ElementsAre(doc1, doc2)); XCTAssertTrue(( snapshot.document_changes() == @@ -105,7 +133,7 @@ - (void)testRemovesDocuments { XCTAssertEqual(snapshot.query(), query); - XCTAssertEqualObjects(snapshot.documents().arrayValue, (@[ doc1, doc3 ])); + XC_ASSERT_THAT(snapshot.documents(), ElementsAre(doc1, doc3)); XCTAssertTrue(( snapshot.document_changes() == @@ -143,10 +171,9 @@ - (void)testDoesNotReturnNilForFirstChanges { - (void)testFiltersDocumentsBasedOnQueryWithFilter { FSTQuery *query = [self queryForMessages]; - FSTRelationFilter *filter = - [FSTRelationFilter filterWithField:testutil::Field("sort") - filterOperator:FSTRelationFilterOperatorLessThanOrEqual - value:[FSTDoubleValue doubleValue:2]]; + FSTRelationFilter *filter = [FSTRelationFilter filterWithField:testutil::Field("sort") + filterOperator:Filter::Operator::LessThanOrEqual + value:[FSTDoubleValue doubleValue:2]]; query = [query queryByAddingFilter:filter]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; @@ -168,7 +195,7 @@ - (void)testFiltersDocumentsBasedOnQueryWithFilter { XCTAssertEqual(snapshot.query(), query); - XCTAssertEqualObjects(snapshot.documents().arrayValue, (@[ doc1, doc5, doc2 ])); + XC_ASSERT_THAT(snapshot.documents(), ElementsAre(doc1, doc5, doc2)); XCTAssertTrue(( snapshot.document_changes() == @@ -182,10 +209,9 @@ - (void)testFiltersDocumentsBasedOnQueryWithFilter { - (void)testUpdatesDocumentsBasedOnQueryWithFilter { FSTQuery *query = [self queryForMessages]; - FSTRelationFilter *filter = - [FSTRelationFilter filterWithField:testutil::Field("sort") - filterOperator:FSTRelationFilterOperatorLessThanOrEqual - value:[FSTDoubleValue doubleValue:2]]; + FSTRelationFilter *filter = [FSTRelationFilter filterWithField:testutil::Field("sort") + filterOperator:Filter::Operator::LessThanOrEqual + value:[FSTDoubleValue doubleValue:2]]; query = [query queryByAddingFilter:filter]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; @@ -202,7 +228,7 @@ - (void)testUpdatesDocumentsBasedOnQueryWithFilter { XCTAssertEqual(snapshot.query(), query); - XCTAssertEqualObjects(snapshot.documents().arrayValue, (@[ doc1, doc3 ])); + XC_ASSERT_THAT(snapshot.documents(), ElementsAre(doc1, doc3)); FSTDocument *newDoc2 = FSTTestDoc("rooms/eros/messages/2", 1, @{@"sort" : @2}, FSTDocumentStateSynced); @@ -215,13 +241,12 @@ - (void)testUpdatesDocumentsBasedOnQueryWithFilter { XCTAssertEqual(snapshot.query(), query); - XCTAssertEqualObjects(snapshot.documents().arrayValue, (@[ newDoc4, doc1, newDoc2 ])); + XC_ASSERT_THAT(snapshot.documents(), ElementsAre(newDoc4, doc1, newDoc2)); - XCTAssertTrue((snapshot.document_changes() == - std::vector{ - DocumentViewChange{doc3, DocumentViewChange::Type::kRemoved}, - DocumentViewChange{newDoc4, DocumentViewChange::Type::kAdded}, - DocumentViewChange{newDoc2, DocumentViewChange::Type::kAdded}})); + XC_ASSERT_THAT(snapshot.document_changes(), + ElementsAre(DocumentViewChange{doc3, DocumentViewChange::Type::kRemoved}, + DocumentViewChange{newDoc4, DocumentViewChange::Type::kAdded}, + DocumentViewChange{newDoc2, DocumentViewChange::Type::kAdded})); XCTAssertTrue(snapshot.from_cache()); XCTAssertFalse(snapshot.sync_state_changed()); @@ -250,7 +275,7 @@ - (void)testRemovesDocumentsForQueryWithLimit { XCTAssertEqual(snapshot.query(), query); - XCTAssertEqualObjects(snapshot.documents().arrayValue, (@[ doc1, doc2 ])); + XC_ASSERT_THAT(snapshot.documents(), ElementsAre(doc1, doc2)); XCTAssertTrue(( snapshot.document_changes() == @@ -301,13 +326,11 @@ - (void)testDoesntReportChangesForDocumentBeyondLimitOfQuery { XCTAssertEqual(snapshot.query(), query); - XCTAssertEqualObjects(snapshot.documents().arrayValue, (@[ doc1, doc3 ])); + XC_ASSERT_THAT(snapshot.documents(), ElementsAre(doc1, doc3)); - XCTAssertTrue((snapshot.document_changes() == - std::vector{ - DocumentViewChange{doc2, DocumentViewChange::Type::kRemoved}, - DocumentViewChange{doc3, DocumentViewChange::Type::kAdded}, - })); + XC_ASSERT_THAT(snapshot.document_changes(), + ElementsAre(DocumentViewChange{doc2, DocumentViewChange::Type::kRemoved}, + DocumentViewChange{doc3, DocumentViewChange::Type::kAdded})); XCTAssertFalse(snapshot.from_cache()); XCTAssertTrue(snapshot.sync_state_changed()); @@ -373,13 +396,6 @@ - (void)testResumingQueryCreatesNoLimbos { XCTAssertEqualObjects(change.limboChanges, @[]); } -- (void)assertDocSet:(FSTDocumentSet *)docSet containsDocs:(NSArray *)docs { - XCTAssertEqual(docs.count, docSet.count); - for (FSTDocument *doc in docs) { - XCTAssertTrue([docSet containsKey:doc.key]); - } -} - - (void)testReturnsNeedsRefillOnDeleteInLimitQuery { FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2]; FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced); @@ -389,7 +405,7 @@ - (void)testReturnsNeedsRefillOnDeleteInLimitQuery { // Start with a full view. FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(2, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -397,12 +413,12 @@ - (void)testReturnsNeedsRefillOnDeleteInLimitQuery { // Remove one of the docs. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc( "rooms/eros/messages/0", 0, NO) ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc2})); XCTAssertTrue(changes.needsRefill); XCTAssertEqual(1, changes.changeSet.GetChanges().size()); // Refill it with just the one doc remaining. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ]) previousChanges:changes]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc2})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(1, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -425,7 +441,7 @@ - (void)testReturnsNeedsRefillOnReorderInLimitQuery { // Start with a full view. FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(2, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -433,13 +449,13 @@ - (void)testReturnsNeedsRefillOnReorderInLimitQuery { // Move one of the docs. doc2 = FSTTestDoc("rooms/eros/messages/1", 1, @{@"order" : @2000}, FSTDocumentStateSynced); changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2})); XCTAssertTrue(changes.needsRefill); XCTAssertEqual(1, changes.changeSet.GetChanges().size()); // Refill it with all three current docs. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3 ]) previousChanges:changes]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc3 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc3})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(2, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -466,7 +482,7 @@ - (void)testDoesntNeedRefillOnReorderWithinLimit { // Start with a full view. FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3, doc4, doc5 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2, doc3})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(3, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -474,7 +490,7 @@ - (void)testDoesntNeedRefillOnReorderWithinLimit { // Move one of the docs. doc1 = FSTTestDoc("rooms/eros/messages/0", 1, @{@"order" : @3}, FSTDocumentStateSynced); changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc2, doc3, doc1 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc2, doc3, doc1})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(1, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -501,7 +517,7 @@ - (void)testDoesntNeedRefillOnReorderAfterLimitQuery { // Start with a full view. FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3, doc4, doc5 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2, doc3})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(3, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -509,7 +525,7 @@ - (void)testDoesntNeedRefillOnReorderAfterLimitQuery { // Move one of the docs. doc4 = FSTTestDoc("rooms/eros/messages/3", 1, @{@"order" : @6}, FSTDocumentStateSynced); changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc4 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2, doc3})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(0, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -524,7 +540,7 @@ - (void)testDoesntNeedRefillForAdditionAfterTheLimit { // Start with a full view. FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(2, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -532,7 +548,7 @@ - (void)testDoesntNeedRefillForAdditionAfterTheLimit { // Add a doc that is past the limit. FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 1, @{}, FSTDocumentStateSynced); changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(0, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -546,7 +562,7 @@ - (void)testDoesntNeedRefillForDeletionsWhenNotNearTheLimit { FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(2, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -554,7 +570,7 @@ - (void)testDoesntNeedRefillForDeletionsWhenNotNearTheLimit { // Remove one of the docs. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc( "rooms/eros/messages/1", 0, NO) ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(1, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -569,7 +585,7 @@ - (void)testHandlesApplyingIrrelevantDocs { // Start with a full view. FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(2, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -577,7 +593,7 @@ - (void)testHandlesApplyingIrrelevantDocs { // Remove a doc that isn't even in the results. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc( "rooms/eros/messages/2", 0, NO) ])]; - [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]]; + XC_ASSERT_THAT(changes.documentSet, ContainsDocs({doc1, doc2})); XCTAssertFalse(changes.needsRefill); XCTAssertEqual(0, changes.changeSet.GetChanges().size()); [view applyChangesToDocuments:changes]; @@ -694,28 +710,24 @@ - (void)testSuppressesWriteAcknowledgementIfWatchHasNotCaughtUp { [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])]; FSTViewChange *viewChange = [view applyChangesToDocuments:changes]; - XCTAssertTrue((viewChange.snapshot.value().document_changes() == - std::vector{ - DocumentViewChange{doc1, DocumentViewChange::Type::kAdded}, - DocumentViewChange{doc2, DocumentViewChange::Type::kAdded}, - })); + XC_ASSERT_THAT(viewChange.snapshot.value().document_changes(), + ElementsAre(DocumentViewChange{doc1, DocumentViewChange::Type::kAdded}, + DocumentViewChange{doc2, DocumentViewChange::Type::kAdded})); changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1Committed, doc2Modified ])]; viewChange = [view applyChangesToDocuments:changes]; // The 'doc1Committed' update is suppressed - XCTAssertTrue((viewChange.snapshot.value().document_changes() == - std::vector{ - DocumentViewChange{doc2Modified, DocumentViewChange::Type::kModified}, - })); + XC_ASSERT_THAT( + viewChange.snapshot.value().document_changes(), + ElementsAre(DocumentViewChange{doc2Modified, DocumentViewChange::Type::kModified})); changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1Acknowledged, doc2Acknowledged ])]; viewChange = [view applyChangesToDocuments:changes]; - XCTAssertTrue((viewChange.snapshot.value().document_changes() == - std::vector{ - DocumentViewChange{doc1Acknowledged, DocumentViewChange::Type::kModified}, - DocumentViewChange{doc2Acknowledged, DocumentViewChange::Type::kMetadata}, - })); + XC_ASSERT_THAT( + viewChange.snapshot.value().document_changes(), + ElementsAre(DocumentViewChange{doc1Acknowledged, DocumentViewChange::Type::kModified}, + DocumentViewChange{doc2Acknowledged, DocumentViewChange::Type::kMetadata})); } @end diff --git a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm index 6782563908e..89f6225c621 100644 --- a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm @@ -1040,7 +1040,8 @@ - (void)testUpdateFieldsWithDots { [self writeDocumentRef:doc data:@{@"a.b" : @"old", @"c.d" : @"old"}]; - [self updateDocumentRef:doc data:@{[[FIRFieldPath alloc] initWithFields:@[ @"a.b" ]] : @"new"}]; + [self updateDocumentRef:doc + data:@{(id)[[FIRFieldPath alloc] initWithFields:@[ @"a.b" ]] : @"new"}]; XCTestExpectation *expectation = [self expectationWithDescription:@"testUpdateFieldsWithDots"]; @@ -1065,8 +1066,8 @@ - (void)testUpdateNestedFields { [self updateDocumentRef:doc data:@{ - @"a.b" : @"new", - [[FIRFieldPath alloc] initWithFields:@[ @"c", @"d" ]] : @"new" + (id) @"a.b" : @"new", + (id)[[FIRFieldPath alloc] initWithFields:@[ @"c", @"d" ]] : @"new" }]; XCTestExpectation *expectation = [self expectationWithDescription:@"testUpdateNestedFields"]; diff --git a/Firestore/Example/Tests/Integration/API/FIRFieldsTests.mm b/Firestore/Example/Tests/Integration/API/FIRFieldsTests.mm index be27c1857af..359dd412697 100644 --- a/Firestore/Example/Tests/Integration/API/FIRFieldsTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRFieldsTests.mm @@ -163,8 +163,8 @@ - (void)testFieldsWithSpecialCharsCanBeUpdated { [self writeDocumentRef:doc data:testData]; [self updateDocumentRef:doc data:@{ - [[FIRFieldPath alloc] initWithFields:@[ @"b.dot" ]] : @100, - @"c\\slash" : @200 + (id)[[FIRFieldPath alloc] initWithFields:@[ @"b.dot" ]] : @100, + (id) @"c\\slash" : @200 }]; FIRDocumentSnapshot *result = [self readDocumentForRef:doc]; diff --git a/Firestore/Example/Tests/Integration/API/FIRFirestoreSourceTests.mm b/Firestore/Example/Tests/Integration/API/FIRFirestoreSourceTests.mm index 0074752b359..3c93f4d542c 100644 --- a/Firestore/Example/Tests/Integration/API/FIRFirestoreSourceTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRFirestoreSourceTests.mm @@ -69,6 +69,30 @@ - (void)testGetCollectionWhileOnlineWithDefaultSource { ])); } +- (void)testGetDocumentError { + FIRDocumentReference *doc = [self.db documentWithPath:@"foo/__invalid__"]; + + XCTestExpectation *completed = [self expectationWithDescription:@"get completed"]; + [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) { + XCTAssertNotNil(error); + [completed fulfill]; + }]; + + [self awaitExpectations]; +} + +- (void)testGetCollectionError { + FIRCollectionReference *col = [self.db collectionWithPath:@"__invalid__"]; + + XCTestExpectation *completed = [self expectationWithDescription:@"get completed"]; + [col getDocumentsWithCompletion:^(FIRQuerySnapshot *snapshot, NSError *error) { + XCTAssertNotNil(error); + [completed fulfill]; + }]; + + [self awaitExpectations]; +} + - (void)testGetDocumentWhileOfflineWithDefaultSource { FIRDocumentReference *doc = [self documentRef]; diff --git a/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.mm b/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.mm index 036ab32b7a1..9cb33c2438e 100644 --- a/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.mm @@ -128,4 +128,30 @@ - (void)testCanBeRemovedIndependently { [two remove]; } +- (void)testCanOutliveDocumentReference { + FIRCollectionReference *collectionRef = [self collectionRef]; + + XCTestExpectation *seen = [self expectationWithDescription:@"seen document"]; + + __block id registration; + NSString *documentID; + @autoreleasepool { + FIRDocumentReference *docRef = [collectionRef documentWithAutoID]; + documentID = docRef.documentID; + registration = [docRef addSnapshotListener:^(FIRDocumentSnapshot *snapshot, NSError *error) { + if (snapshot.exists) { + [seen fulfill]; + } + }]; + docRef = nil; + } + + XCTAssertNotNil(registration); + + FIRDocumentReference *docRef2 = [collectionRef documentWithPath:documentID]; + [self writeDocumentRef:docRef2 data:@{@"foo" : @"bar"}]; + + [registration remove]; +} + @end diff --git a/Firestore/Example/Tests/Integration/API/FIRNumericTransformTests.mm b/Firestore/Example/Tests/Integration/API/FIRNumericTransformTests.mm index 8cd1fa1e54b..fa742025cd0 100644 --- a/Firestore/Example/Tests/Integration/API/FIRNumericTransformTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRNumericTransformTests.mm @@ -70,9 +70,9 @@ - (void)writeInitialData:(NSDictionary *)data { - (void)expectLocalAndRemoteValue:(int64_t)expectedSum { FIRDocumentSnapshot *snap = [_accumulator awaitLocalEvent]; - XCTAssertEqual(@(expectedSum), snap[@"sum"]); + XCTAssertEqualObjects(@(expectedSum), snap[@"sum"]); snap = [_accumulator awaitRemoteEvent]; - XCTAssertEqual(@(expectedSum), snap[@"sum"]); + XCTAssertEqualObjects(@(expectedSum), snap[@"sum"]); } - (void)expectApproximateLocalAndRemoteValue:(double)expectedSum { diff --git a/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm index da20d92ea26..af7be097953 100644 --- a/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm @@ -294,4 +294,112 @@ - (void)testArrayContainsQueries { // of anything else interesting to test. } +- (void)testCollectionGroupQueries { + // Use .document() to get a random collection group name to use but ensure it starts with 'b' + // for predictable ordering. + NSString *collectionGroup = [NSString + stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID]; + + NSArray *docPaths = @[ + @"abc/123/${collectionGroup}/cg-doc1", @"abc/123/${collectionGroup}/cg-doc2", + @"${collectionGroup}/cg-doc3", @"${collectionGroup}/cg-doc4", + @"def/456/${collectionGroup}/cg-doc5", @"${collectionGroup}/virtual-doc/nested-coll/not-cg-doc", + @"x${collectionGroup}/not-cg-doc", @"${collectionGroup}x/not-cg-doc", + @"abc/123/${collectionGroup}x/not-cg-doc", @"abc/123/x${collectionGroup}/not-cg-doc", + @"abc/${collectionGroup}" + ]; + + FIRWriteBatch *batch = [self.db batch]; + for (NSString *docPath in docPaths) { + NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}" + withString:collectionGroup]; + [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]]; + } + XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"]; + [batch commitWithCompletion:^(NSError *error) { + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self awaitExpectations]; + + FIRQuerySnapshot *querySnapshot = + [self readDocumentSetForRef:[self.db collectionGroupWithID:collectionGroup]]; + NSArray *ids = FIRQuerySnapshotGetIDs(querySnapshot); + XCTAssertEqualObjects(ids, (@[ @"cg-doc1", @"cg-doc2", @"cg-doc3", @"cg-doc4", @"cg-doc5" ])); +} + +- (void)testCollectionGroupQueriesWithStartAtEndAtWithArbitraryDocumentIDs { + // Use .document() to get a random collection group name to use but ensure it starts with 'b' + // for predictable ordering. + NSString *collectionGroup = [NSString + stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID]; + + NSArray *docPaths = @[ + @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2", + @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4", + @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope" + ]; + + FIRWriteBatch *batch = [self.db batch]; + for (NSString *docPath in docPaths) { + NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}" + withString:collectionGroup]; + [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]]; + } + XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"]; + [batch commitWithCompletion:^(NSError *error) { + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self awaitExpectations]; + + FIRQuerySnapshot *querySnapshot = [self + readDocumentSetForRef:[[[[self.db collectionGroupWithID:collectionGroup] + queryOrderedByFieldPath:[FIRFieldPath documentID]] + queryStartingAfterValues:@[ @"a/b" ]] + queryEndingBeforeValues:@[ + [NSString stringWithFormat:@"a/b/%@/cg-doc3", collectionGroup] + ]]]; + + NSArray *ids = FIRQuerySnapshotGetIDs(querySnapshot); + XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ])); +} + +- (void)testCollectionGroupQueriesWithWhereFiltersOnArbitraryDocumentIDs { + // Use .document() to get a random collection group name to use but ensure it starts with 'b' + // for predictable ordering. + NSString *collectionGroup = [NSString + stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID]; + + NSArray *docPaths = @[ + @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2", + @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4", + @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope" + ]; + + FIRWriteBatch *batch = [self.db batch]; + for (NSString *docPath in docPaths) { + NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}" + withString:collectionGroup]; + [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]]; + } + XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"]; + [batch commitWithCompletion:^(NSError *error) { + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self awaitExpectations]; + + FIRQuerySnapshot *querySnapshot = [self + readDocumentSetForRef:[[[self.db collectionGroupWithID:collectionGroup] + queryWhereFieldPath:[FIRFieldPath documentID] + isGreaterThanOrEqualTo:@"a/b"] + queryWhereFieldPath:[FIRFieldPath documentID] + isLessThan:[NSString stringWithFormat:@"a/b/%@/cg-doc3", + collectionGroup]]]; + + NSArray *ids = FIRQuerySnapshotGetIDs(querySnapshot); + XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ])); +} + @end diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm index ba5fa55a2db..4af81d26556 100644 --- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm @@ -264,7 +264,7 @@ - (void)testServerTimestampsPreviousValueFromLocalMutation { - (void)testServerTimestampsWorkViaTransactionSet { [self runTransactionBlock:^(FIRTransaction *transaction) { - [transaction setData:_setData forDocument:_docRef]; + [transaction setData:self->_setData forDocument:self->_docRef]; }]; [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]]; @@ -273,7 +273,7 @@ - (void)testServerTimestampsWorkViaTransactionSet { - (void)testServerTimestampsWorkViaTransactionUpdate { [self writeInitialData]; [self runTransactionBlock:^(FIRTransaction *transaction) { - [transaction updateData:_updateData forDocument:_docRef]; + [transaction updateData:self->_updateData forDocument:self->_docRef]; }]; [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]]; } @@ -294,7 +294,7 @@ - (void)testServerTimestampsFailViaTransactionUpdateOnNonexistentDocument { XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"]; [_docRef.firestore runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) { - [transaction updateData:_updateData forDocument:_docRef]; + [transaction updateData:self->_updateData forDocument:self->_docRef]; return nil; } completion:^(id result, NSError *error) { diff --git a/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm index b1fec91d657..002da854436 100644 --- a/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm @@ -18,6 +18,8 @@ #import +#include + #import "Firestore/Source/API/FIRFieldValue+Internal.h" #import "Firestore/Example/Tests/Util/FSTHelpers.h" @@ -36,14 +38,14 @@ @implementation FIRValidationTests - (void)testNilHostFails { FIRFirestoreSettings *settings = self.db.settings; FSTAssertThrows(settings.host = nil, - @"host setting may not be nil. You should generally just use the default value " + @"Host setting may not be nil. You should generally just use the default value " "(which is firestore.googleapis.com)"); } - (void)testNilDispatchQueueFails { FIRFirestoreSettings *settings = self.db.settings; FSTAssertThrows(settings.dispatchQueue = nil, - @"dispatch queue setting may not be nil. Create a new dispatch queue with " + @"Dispatch queue setting may not be nil. Create a new dispatch queue with " "dispatch_queue_create(\"com.example.MyQueue\", NULL) or just use the default " "(which is the main queue, returned from dispatch_get_main_queue())"); } @@ -226,7 +228,7 @@ - (void)testWritesWithInvalidTypesFail { } - (void)testWritesWithLargeNumbersFail { - NSNumber *num = @((unsigned long long)LONG_MAX + 1); + NSNumber *num = @(static_cast(std::numeric_limits::max()) + 1); NSString *reason = [NSString stringWithFormat:@"NSNumber (%@) is too large (found in field num)", num]; [self expectWrite:@{@"num" : num} toFailWithReason:reason]; @@ -390,18 +392,18 @@ - (void)testQueryWithNonPositiveLimitFails { - (void)testNonEqualityQueriesOnNullOrNaNFail { FSTAssertThrows([[self collectionRef] queryWhereField:@"a" isGreaterThan:nil], - @"Invalid Query. You can only perform equality comparisons on nil / NSNull."); + @"Invalid Query. Nil and NSNull only support equality comparisons."); FSTAssertThrows([[self collectionRef] queryWhereField:@"a" isGreaterThan:[NSNull null]], - @"Invalid Query. You can only perform equality comparisons on nil / NSNull."); + @"Invalid Query. Nil and NSNull only support equality comparisons."); FSTAssertThrows([[self collectionRef] queryWhereField:@"a" arrayContains:nil], - @"Invalid Query. You can only perform equality comparisons on nil / NSNull."); + @"Invalid Query. Nil and NSNull only support equality comparisons."); FSTAssertThrows([[self collectionRef] queryWhereField:@"a" arrayContains:[NSNull null]], - @"Invalid Query. You can only perform equality comparisons on nil / NSNull."); + @"Invalid Query. Nil and NSNull only support equality comparisons."); FSTAssertThrows([[self collectionRef] queryWhereField:@"a" isGreaterThan:@(NAN)], - @"Invalid Query. You can only perform equality comparisons on NaN."); + @"Invalid Query. NaN only supports equality comparisons."); FSTAssertThrows([[self collectionRef] queryWhereField:@"a" arrayContains:@(NAN)], - @"Invalid Query. You can only perform equality comparisons on NaN."); + @"Invalid Query. NaN only supports equality comparisons."); } - (void)testQueryCannotBeCreatedFromDocumentsMissingSortValues { @@ -482,12 +484,19 @@ - (void)testQueryBoundMustNotHaveMoreComponentsThanSortOrders { } - (void)testQueryOrderedByKeyBoundMustBeAStringWithoutSlashes { - FIRCollectionReference *testCollection = [self collectionRef]; - FIRQuery *query = [testCollection queryOrderedByFieldPath:[FIRFieldPath documentID]]; + FIRQuery *query = [[self.db collectionWithPath:@"collection"] + queryOrderedByFieldPath:[FIRFieldPath documentID]]; + FIRQuery *cgQuery = [[self.db collectionGroupWithID:@"collection"] + queryOrderedByFieldPath:[FIRFieldPath documentID]]; FSTAssertThrows([query queryStartingAtValues:@[ @1 ]], @"Invalid query. Expected a string for the document ID."); FSTAssertThrows([query queryStartingAtValues:@[ @"foo/bar" ]], - @"Invalid query. Document ID 'foo/bar' contains a slash."); + @"Invalid query. When querying a collection and ordering by document " + "ID, you must pass a plain document ID, but 'foo/bar' contains a slash."); + FSTAssertThrows([cgQuery queryStartingAtValues:@[ @"foo" ]], + @"Invalid query. When querying a collection group and ordering by " + "document ID, you must pass a value that results in a valid document path, " + "but 'foo' is not because it contains an odd number of segments."); } - (void)testQueryMustNotSpecifyStartingOrEndingPointAfterOrder { @@ -508,8 +517,8 @@ - (void)testQueriesFilteredByDocumentIDMustUseStringsOrDocumentReferences { "document ID, but it was an empty string."; FSTAssertThrows([collection queryWhereFieldPath:[FIRFieldPath documentID] isEqualTo:@""], reason); - reason = @"Invalid query. When querying by document ID you must provide a valid document ID, " - "but 'foo/bar/baz' contains a '/' character."; + reason = @"Invalid query. When querying a collection by document ID you must provide a " + "plain document ID, but 'foo/bar/baz' contains a '/' character."; FSTAssertThrows( [collection queryWhereFieldPath:[FIRFieldPath documentID] isEqualTo:@"foo/bar/baz"], reason); @@ -517,6 +526,14 @@ - (void)testQueriesFilteredByDocumentIDMustUseStringsOrDocumentReferences { "DocumentReference, but it was of type: __NSCFNumber"; FSTAssertThrows([collection queryWhereFieldPath:[FIRFieldPath documentID] isEqualTo:@1], reason); + reason = @"Invalid query. When querying a collection group by document ID, the value " + "provided must result in a valid document path, but 'foo/bar/baz' is not because it " + "has an odd number of segments."; + FSTAssertThrows( + [[self.db collectionGroupWithID:@"collection"] queryWhereFieldPath:[FIRFieldPath documentID] + isEqualTo:@"foo/bar/baz"], + reason); + reason = @"Invalid query. You can't perform arrayContains queries on document ID since document IDs " "are not arrays."; @@ -685,7 +702,7 @@ - (void)expectFieldPath:(NSString *)fieldPath toFailWithReason:(NSString *)reaso - (void)verifyExceptionForInvalidLatitude:(double)latitude { NSString *reason = [NSString - stringWithFormat:@"GeoPoint requires a latitude value in the range of [-90, 90], but was %f", + stringWithFormat:@"GeoPoint requires a latitude value in the range of [-90, 90], but was %g", latitude]; FSTAssertThrows([[FIRGeoPoint alloc] initWithLatitude:latitude longitude:0], reason); } @@ -693,7 +710,7 @@ - (void)verifyExceptionForInvalidLatitude:(double)latitude { - (void)verifyExceptionForInvalidLongitude:(double)longitude { NSString *reason = [NSString stringWithFormat: - @"GeoPoint requires a longitude value in the range of [-180, 180], but was %f", + @"GeoPoint requires a longitude value in the range of [-180, 180], but was %g", longitude]; FSTAssertThrows([[FIRGeoPoint alloc] initWithLatitude:0 longitude:longitude], reason); } diff --git a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm index e3d3199ef42..325abd95c33 100644 --- a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm +++ b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm @@ -246,7 +246,7 @@ - (FSTSetMutation *)setMutation { return [[FSTSetMutation alloc] initWithKey:DocumentKey::FromPathString("rooms/eros") value:[[FSTObjectValue alloc] - initWithDictionary:@{@"name" : [FSTStringValue stringValue:@"Eros"]}] + initWithDictionary:@{@"name" : FieldValue::FromString("Eros").Wrap()}] precondition:Precondition::None()]; } diff --git a/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.mm b/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.mm index 41d185d4dea..c4c9915e719 100644 --- a/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.mm +++ b/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.mm @@ -398,7 +398,7 @@ - (void)testRemoveQueriesUpThroughSequenceNumber { XCTAssertEqual(10, removed); // Make sure we removed the even targets with targetID <= 20. _persistence.run("verify remaining targets are > 20 or odd", [&]() { - _queryCache->EnumerateTargets(^(FSTQueryData *queryData, BOOL *stop) { + _queryCache->EnumerateTargets([&](FSTQueryData *queryData) { XCTAssertTrue(queryData.targetID > 20 || queryData.targetID % 2 == 1); }); }); diff --git a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm index 79a9b977e1c..9a48c5b0a17 100644 --- a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm +++ b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm @@ -16,6 +16,7 @@ #import +#include #include #include #include @@ -37,6 +38,7 @@ NS_ASSUME_NONNULL_BEGIN using firebase::firestore::FirestoreErrorCode; +using firebase::firestore::local::LevelDbCollectionParentKey; using firebase::firestore::local::LevelDbDocumentMutationKey; using firebase::firestore::local::LevelDbDocumentTargetKey; using firebase::firestore::local::LevelDbMigrations; @@ -334,7 +336,6 @@ - (void)testRemovesMutationBatches { // Verify std::string buffer; LevelDbTransaction transaction(_db.get(), "Verify"); - auto it = transaction.NewIterator(); // verify that we deleted the correct batches XCTAssertTrue(transaction.Get(LevelDbMutationKey::Key("foo", 1), &buffer).IsNotFound()); XCTAssertTrue(transaction.Get(LevelDbMutationKey::Key("foo", 2), &buffer).IsNotFound()); @@ -360,6 +361,60 @@ - (void)testRemovesMutationBatches { } } +- (void)testCreateCollectionParentsIndex { + // This test creates a database with schema version 5 that has a few + // mutations and a few remote documents and then ensures that appropriate + // entries are written to the collectionParentIndex. + std::vector write_paths{"cg1/x", "cg1/y", "cg1/x/cg1/x", "cg2/x", "cg1/x/cg2/x"}; + std::vector remote_doc_paths{"cg1/z", "cg1/y/cg1/x", "cg2/x/cg3/x", + "blah/x/blah/x/cg3/x"}; + std::map> expected_parents{ + {"cg1", {"", "cg1/x", "cg1/y"}}, {"cg2", {"", "cg1/x"}}, {"cg3", {"blah/x/blah/x", "cg2/x"}}}; + + std::string empty_buffer; + LevelDbMigrations::RunMigrations(_db.get(), 5); + { + LevelDbTransaction transaction(_db.get(), "Write Mutations and Remote Documents"); + // Write mutations. + for (auto write_path : write_paths) { + // We "cheat" and only write the DbDocumentMutation index entries, since + // that's all the migration uses. + DocumentKey key = DocumentKey::FromPathString(write_path); + transaction.Put(LevelDbDocumentMutationKey::Key("dummy-uid", key, /*dummy batchId=*/123), + empty_buffer); + } + + // Write remote document entries. + for (auto remote_doc_path : remote_doc_paths) { + DocumentKey key = DocumentKey::FromPathString(remote_doc_path); + transaction.Put(LevelDbRemoteDocumentKey::Key(key), empty_buffer); + } + + transaction.Commit(); + } + + // Migrate to v6 and verify index entries. + LevelDbMigrations::RunMigrations(_db.get(), 6); + { + LevelDbTransaction transaction(_db.get(), "Verify"); + + std::map> actual_parents; + auto index_iterator = transaction.NewIterator(); + std::string index_prefix = LevelDbCollectionParentKey::KeyPrefix(); + LevelDbCollectionParentKey row_key; + for (index_iterator->Seek(index_prefix); index_iterator->Valid(); index_iterator->Next()) { + if (!absl::StartsWith(index_iterator->key(), index_prefix) || + !row_key.Decode(index_iterator->key())) + break; + + std::vector &parents = actual_parents[row_key.collection_id()]; + parents.push_back(row_key.parent().CanonicalString()); + } + + XCTAssertEqual(actual_parents, expected_parents); + } +} + - (void)testCanDowngrade { // First, run all of the migrations LevelDbMigrations::RunMigrations(_db.get()); diff --git a/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm index 15d30c7d6eb..79d906a5d18 100644 --- a/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm +++ b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm @@ -28,7 +28,6 @@ #import "Firestore/Source/Local/FSTPersistence.h" #import "Firestore/Source/Local/FSTQueryData.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Source/Model/FSTMutation.h" #import "Firestore/Source/Model/FSTMutationBatch.h" #import "Firestore/Source/Util/FSTClasses.h" @@ -40,6 +39,7 @@ #include "Firestore/core/src/firebase/firestore/auth/user.h" #include "Firestore/core/src/firebase/firestore/model/document_map.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/remote/remote_event.h" #include "Firestore/core/src/firebase/firestore/remote/watch_change.h" #include "Firestore/core/src/firebase/firestore/util/status.h" diff --git a/Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.mm b/Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.mm index c39a15f1fbf..7784056705b 100644 --- a/Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.mm +++ b/Firestore/Example/Tests/Local/FSTMemoryRemoteDocumentCacheTests.mm @@ -44,7 +44,7 @@ - (void)setUp { self.persistence = [FSTPersistenceTestHelpers eagerGCMemoryPersistence]; HARD_ASSERT(!_cache, "Previous cache not torn down"); - _cache = absl::make_unique(); + _cache = absl::make_unique(self.persistence); } - (RemoteDocumentCache *)remoteDocumentCache { diff --git a/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm b/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm index 05be1b7eeab..edb2334f9b4 100644 --- a/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm +++ b/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm @@ -14,13 +14,19 @@ * limitations under the License. */ -#import "Firestore/Source/Model/FSTDocumentSet.h" - #import -#import "Firestore/Source/Model/FSTDocument.h" +#include #import "Firestore/Example/Tests/Util/FSTHelpers.h" +#import "Firestore/Source/Model/FSTDocument.h" + +// TODO(wilhuff) move to first include once this test filename matches +#include "Firestore/core/src/firebase/firestore/model/document_set.h" +#include "Firestore/core/test/firebase/firestore/testutil/xcgmock.h" + +using firebase::firestore::model::DocumentSet; +using testing::ElementsAre; NS_ASSUME_NONNULL_BEGIN @@ -44,91 +50,93 @@ - (void)setUp { } - (void)testCount { - XCTAssertEqual([FSTTestDocSet(_comp, @[]) count], 0); - XCTAssertEqual([FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]) count], 3); + XCTAssertEqual(FSTTestDocSet(_comp, @[]).size(), 0); + XCTAssertEqual(FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]).size(), 3); } - (void)testHasKey { - FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, _doc2 ]); + DocumentSet set = FSTTestDocSet(_comp, @[ _doc1, _doc2 ]); - XCTAssertTrue([set containsKey:_doc1.key]); - XCTAssertTrue([set containsKey:_doc2.key]); - XCTAssertFalse([set containsKey:_doc3.key]); + XCTAssertTrue(set.ContainsKey(_doc1.key)); + XCTAssertTrue(set.ContainsKey(_doc2.key)); + XCTAssertFalse(set.ContainsKey(_doc3.key)); } - (void)testDocumentForKey { - FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, _doc2 ]); + DocumentSet set = FSTTestDocSet(_comp, @[ _doc1, _doc2 ]); - XCTAssertEqualObjects([set documentForKey:_doc1.key], _doc1); - XCTAssertEqualObjects([set documentForKey:_doc2.key], _doc2); - XCTAssertNil([set documentForKey:_doc3.key]); + XCTAssertEqualObjects(set.GetDocument(_doc1.key), _doc1); + XCTAssertEqualObjects(set.GetDocument(_doc2.key), _doc2); + XCTAssertNil(set.GetDocument(_doc3.key)); } - (void)testFirstAndLastDocument { - FSTDocumentSet *set = FSTTestDocSet(_comp, @[]); - XCTAssertNil([set firstDocument]); - XCTAssertNil([set lastDocument]); + DocumentSet set = FSTTestDocSet(_comp, @[]); + XCTAssertNil(set.GetFirstDocument()); + XCTAssertNil(set.GetLastDocument()); set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); - XCTAssertEqualObjects([set firstDocument], _doc3); - XCTAssertEqualObjects([set lastDocument], _doc2); + XCTAssertEqualObjects(set.GetFirstDocument(), _doc3); + XCTAssertEqualObjects(set.GetLastDocument(), _doc2); } - (void)testKeepsDocumentsInTheRightOrder { - FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); - XCTAssertEqualObjects([[set documentEnumerator] allObjects], (@[ _doc3, _doc1, _doc2 ])); + DocumentSet set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); + XC_ASSERT_THAT(set, ElementsAre(_doc3, _doc1, _doc2)); } - (void)testDeletes { - FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); + DocumentSet set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); - FSTDocumentSet *setWithoutDoc1 = [set documentSetByRemovingKey:_doc1.key]; - XCTAssertEqualObjects([[setWithoutDoc1 documentEnumerator] allObjects], (@[ _doc3, _doc2 ])); - XCTAssertEqual([setWithoutDoc1 count], 2); + DocumentSet setWithoutDoc1 = set.erase(_doc1.key); + XC_ASSERT_THAT(setWithoutDoc1, ElementsAre(_doc3, _doc2)); + XCTAssertEqual(setWithoutDoc1.size(), 2); // Original remains unchanged - XCTAssertEqualObjects([[set documentEnumerator] allObjects], (@[ _doc3, _doc1, _doc2 ])); + XC_ASSERT_THAT(set, ElementsAre(_doc3, _doc1, _doc2)); - FSTDocumentSet *setWithoutDoc3 = [setWithoutDoc1 documentSetByRemovingKey:_doc3.key]; - XCTAssertEqualObjects([[setWithoutDoc3 documentEnumerator] allObjects], (@[ _doc2 ])); - XCTAssertEqual([setWithoutDoc3 count], 1); + DocumentSet setWithoutDoc3 = setWithoutDoc1.erase(_doc3.key); + XC_ASSERT_THAT(setWithoutDoc3, ElementsAre(_doc2)); + XCTAssertEqual(setWithoutDoc3.size(), 1); } - (void)testUpdates { - FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); + DocumentSet set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); FSTDocument *doc2Prime = FSTTestDoc("docs/2", 0, @{@"sort" : @9}, FSTDocumentStateSynced); - set = [set documentSetByAddingDocument:doc2Prime]; - XCTAssertEqual([set count], 3); - XCTAssertEqualObjects([set documentForKey:doc2Prime.key], doc2Prime); - XCTAssertEqualObjects([[set documentEnumerator] allObjects], (@[ _doc3, _doc1, doc2Prime ])); + set = set.insert(doc2Prime); + XCTAssertEqual(set.size(), 3); + XCTAssertEqualObjects(set.GetDocument(doc2Prime.key), doc2Prime); + XC_ASSERT_THAT(set, ElementsAre(_doc3, _doc1, doc2Prime)); } - (void)testAddsDocsWithEqualComparisonValues { FSTDocument *doc4 = FSTTestDoc("docs/4", 0, @{@"sort" : @2}, FSTDocumentStateSynced); - FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, doc4 ]); - XCTAssertEqualObjects([[set documentEnumerator] allObjects], (@[ _doc1, doc4 ])); + DocumentSet set = FSTTestDocSet(_comp, @[ _doc1, doc4 ]); + XC_ASSERT_THAT(set, ElementsAre(_doc1, doc4)); } - (void)testIsEqual { - FSTDocumentSet *set1 = FSTTestDocSet(FSTDocumentComparatorByKey, @[ _doc1, _doc2, _doc3 ]); - FSTDocumentSet *set2 = FSTTestDocSet(FSTDocumentComparatorByKey, @[ _doc1, _doc2, _doc3 ]); - XCTAssertEqualObjects(set1, set1); - XCTAssertEqualObjects(set1, set2); - XCTAssertNotEqualObjects(set1, nil); - - FSTDocumentSet *sortedSet1 = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); - FSTDocumentSet *sortedSet2 = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); - XCTAssertEqualObjects(sortedSet1, sortedSet1); - XCTAssertEqualObjects(sortedSet1, sortedSet2); - XCTAssertNotEqualObjects(sortedSet1, nil); - - FSTDocumentSet *shortSet = FSTTestDocSet(FSTDocumentComparatorByKey, @[ _doc1, _doc2 ]); - XCTAssertNotEqualObjects(set1, shortSet); - XCTAssertNotEqualObjects(set1, sortedSet1); + DocumentSet empty{FSTDocumentComparatorByKey}; + DocumentSet set1 = FSTTestDocSet(FSTDocumentComparatorByKey, @[ _doc1, _doc2, _doc3 ]); + DocumentSet set2 = FSTTestDocSet(FSTDocumentComparatorByKey, @[ _doc1, _doc2, _doc3 ]); + XCTAssertEqual(set1, set1); + XCTAssertEqual(set1, set2); + XCTAssertNotEqual(set1, empty); + + DocumentSet sortedSet1 = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); + DocumentSet sortedSet2 = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); + XCTAssertEqual(sortedSet1, sortedSet1); + XCTAssertEqual(sortedSet1, sortedSet2); + XCTAssertNotEqual(sortedSet1, empty); + + DocumentSet shortSet = FSTTestDocSet(FSTDocumentComparatorByKey, @[ _doc1, _doc2 ]); + XCTAssertNotEqual(set1, shortSet); + XCTAssertNotEqual(set1, sortedSet1); } + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Example/Tests/Model/FSTDocumentTests.mm b/Firestore/Example/Tests/Model/FSTDocumentTests.mm index ae8db452d95..6db2a6c842e 100644 --- a/Firestore/Example/Tests/Model/FSTDocumentTests.mm +++ b/Firestore/Example/Tests/Model/FSTDocumentTests.mm @@ -64,9 +64,9 @@ - (void)testExtractsFields { state:FSTDocumentStateSynced]; XCTAssertEqualObjects([doc fieldForPath:testutil::Field("desc")], - [FSTStringValue stringValue:@"Discuss all the project related stuff"]); + FieldValue::FromString("Discuss all the project related stuff").Wrap()); XCTAssertEqualObjects([doc fieldForPath:testutil::Field("owner.title")], - [FSTStringValue stringValue:@"scallywag"]); + FieldValue::FromString("scallywag").Wrap()); } - (void)testIsEqual { diff --git a/Firestore/Example/Tests/Model/FSTFieldValueTests.mm b/Firestore/Example/Tests/Model/FSTFieldValueTests.mm index 45a07f2c194..05a49313640 100644 --- a/Firestore/Example/Tests/Model/FSTFieldValueTests.mm +++ b/Firestore/Example/Tests/Model/FSTFieldValueTests.mm @@ -27,12 +27,14 @@ #import "Firestore/Example/Tests/Util/FSTHelpers.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" #include "Firestore/core/test/firebase/firestore/testutil/testutil.h" namespace testutil = firebase::firestore::testutil; namespace util = firebase::firestore::util; using firebase::firestore::model::DatabaseId; +using firebase::firestore::model::FieldValue; /** Helper to wrap the values in a set of equality groups using FSTTestFieldValue(). */ NSArray *FSTWrapGroups(NSArray *groups) { @@ -91,6 +93,7 @@ - (void)testWrapIntegers { FSTFieldValue *wrapped = FSTTestFieldValue(value); XCTAssertEqualObjects([wrapped class], [FSTIntegerValue class]); XCTAssertEqualObjects([wrapped value], @([value longLongValue])); + XCTAssertEqual(wrapped.type, FieldValue::Type::Integer); } } @@ -105,6 +108,7 @@ - (void)testWrapsDoubles { FSTFieldValue *wrapped = FSTTestFieldValue(value); XCTAssertEqualObjects([wrapped class], [FSTDoubleValue class]); XCTAssertEqualObjects([wrapped value], value); + XCTAssertEqual(wrapped.type, FieldValue::Type::Double); } } @@ -113,14 +117,16 @@ - (void)testWrapsNilAndNSNull { XCTAssertEqual(FSTTestFieldValue(nil), nullValue); XCTAssertEqual(FSTTestFieldValue([NSNull null]), nullValue); XCTAssertEqual([nullValue value], [NSNull null]); + XCTAssertEqual(nullValue.type, FieldValue::Type::Null); } - (void)testWrapsBooleans { - NSArray *values = @[ @YES, @NO, [NSNumber numberWithChar:1], [NSNumber numberWithChar:0] ]; + NSArray *values = @[ @YES, @NO ]; for (id value in values) { FSTFieldValue *wrapped = FSTTestFieldValue(value); - XCTAssertEqualObjects([wrapped class], [FSTBooleanValue class]); + XCTAssertEqualObjects([wrapped class], [FSTDelegateValue class]); XCTAssertEqualObjects([wrapped value], value); + XCTAssertEqual(wrapped.type, FieldValue::Type::Boolean); } // Unsigned chars could conceivably be handled consistently with signed chars but on arm64 these @@ -217,8 +223,9 @@ - (void)testWrapStrings { NSArray *values = @[ @"", @"abc" ]; for (id value in values) { FSTFieldValue *wrapped = FSTTestFieldValue(value); - XCTAssertEqualObjects([wrapped class], [FSTStringValue class]); + XCTAssertEqualObjects([wrapped class], [FSTDelegateValue class]); XCTAssertEqualObjects([wrapped value], value); + XCTAssertEqual(wrapped.type, FieldValue::Type::String); } } @@ -229,6 +236,7 @@ - (void)testWrapDates { XCTAssertEqualObjects([wrapped class], [FSTTimestampValue class]); XCTAssertEqualObjects([[wrapped value] class], [FIRTimestamp class]); XCTAssertEqualObjects([wrapped value], [FIRTimestamp timestampWithDate:value]); + XCTAssertEqual(wrapped.type, FieldValue::Type::Timestamp); } } @@ -239,6 +247,7 @@ - (void)testWrapGeoPoints { FSTFieldValue *wrapped = FSTTestFieldValue(value); XCTAssertEqualObjects([wrapped class], [FSTGeoPointValue class]); XCTAssertEqualObjects([wrapped value], value); + XCTAssertEqual(wrapped.type, FieldValue::Type::GeoPoint); } } @@ -248,6 +257,7 @@ - (void)testWrapBlobs { FSTFieldValue *wrapped = FSTTestFieldValue(value); XCTAssertEqualObjects([wrapped class], [FSTBlobValue class]); XCTAssertEqualObjects([wrapped value], value); + XCTAssertEqual(wrapped.type, FieldValue::Type::Blob); } } @@ -262,35 +272,39 @@ - (void)testWrapResourceNames { XCTAssertEqualObjects([wrapped value], [FSTDocumentKey keyWithDocumentKey:value.key]); XCTAssertTrue(*((FSTReferenceValue *)wrapped).databaseID == *(const DatabaseId *)(value.databaseID)); + XCTAssertEqual(wrapped.type, FieldValue::Type::Reference); } } - (void)testWrapsEmptyObjects { XCTAssertEqualObjects(FSTTestFieldValue(@{}), [FSTObjectValue objectValue]); + XCTAssertEqual(FSTTestFieldValue(@{}).type, FieldValue::Type::Object); } - (void)testWrapsSimpleObjects { FSTObjectValue *actual = FSTTestObjectValue(@{@"a" : @"foo", @"b" : @(1L), @"c" : @YES, @"d" : [NSNull null]}); FSTObjectValue *expected = [[FSTObjectValue alloc] initWithDictionary:@{ - @"a" : [FSTStringValue stringValue:@"foo"], + @"a" : FieldValue::FromString("foo").Wrap(), @"b" : [FSTIntegerValue integerValue:1LL], - @"c" : [FSTBooleanValue trueValue], + @"c" : FieldValue::True().Wrap(), @"d" : [FSTNullValue nullValue] }]; XCTAssertEqualObjects(actual, expected); + XCTAssertEqual(actual.type, FieldValue::Type::Object); } - (void)testWrapsNestedObjects { FSTObjectValue *actual = FSTTestObjectValue(@{@"a" : @{@"b" : @{@"c" : @"foo"}, @"d" : @YES}}); FSTObjectValue *expected = [[FSTObjectValue alloc] initWithDictionary:@{ @"a" : [[FSTObjectValue alloc] initWithDictionary:@{ - @"b" : - [[FSTObjectValue alloc] initWithDictionary:@{@"c" : [FSTStringValue stringValue:@"foo"]}], - @"d" : [FSTBooleanValue booleanValue:YES] + @"b" : [[FSTObjectValue alloc] + initWithDictionary:@{@"c" : FieldValue::FromString("foo").Wrap()}], + @"d" : FieldValue::True().Wrap() }] }]; XCTAssertEqualObjects(actual, expected); + XCTAssertEqual(actual.type, FieldValue::Type::Object); } - (void)testExtractsFields { @@ -298,9 +312,9 @@ - (void)testExtractsFields { FSTAssertIsKindOfClass(obj, FSTObjectValue); FSTAssertIsKindOfClass([obj valueForPath:testutil::Field("foo")], FSTObjectValue); - XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.a")], [FSTBooleanValue trueValue]); + XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.a")], FieldValue::True().Wrap()); XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.b")], - [FSTStringValue stringValue:@"string"]); + FieldValue::FromString("string").Wrap()); XCTAssertNil([obj valueForPath:testutil::Field("foo.a.b")]); XCTAssertNil([obj valueForPath:testutil::Field("bar")]); @@ -410,17 +424,18 @@ - (void)testDeletesNestedKeys { - (void)testArrays { FSTArrayValue *expected = [[FSTArrayValue alloc] - initWithValueNoCopy:@[ [FSTStringValue stringValue:@"value"], [FSTBooleanValue trueValue] ]]; + initWithValueNoCopy:@[ FieldValue::FromString("value").Wrap(), FieldValue::True().Wrap() ]]; FSTArrayValue *actual = (FSTArrayValue *)FSTTestFieldValue(@[ @"value", @YES ]); XCTAssertEqualObjects(actual, expected); + XCTAssertEqual(actual.type, FieldValue::Type::Array); } - (void)testValueEquality { DatabaseId database_id = DatabaseId("project", DatabaseId::kDefault); NSArray *groups = @[ - @[ FSTTestFieldValue(@YES), [FSTBooleanValue booleanValue:YES] ], - @[ FSTTestFieldValue(@NO), [FSTBooleanValue booleanValue:NO] ], + @[ FSTTestFieldValue(@YES), FieldValue::True().Wrap() ], + @[ FSTTestFieldValue(@NO), FieldValue::False().Wrap() ], @[ FSTTestFieldValue([NSNull null]), [FSTNullValue nullValue] ], @[ FSTTestFieldValue(@(0.0 / 0.0)), FSTTestFieldValue(@(NAN)), [FSTDoubleValue nanValue] ], // -0.0 and 0.0 compare: the same (but are not isEqual:) @@ -433,7 +448,7 @@ - (void)testValueEquality { FSTTestFieldValue(FSTTestData(0, 1, 2, -1)), [FSTBlobValue blobValue:FSTTestData(0, 1, 2, -1)] ], @[ FSTTestFieldValue(FSTTestData(0, 1, -1)) ], - @[ FSTTestFieldValue(@"string"), [FSTStringValue stringValue:@"string"] ], + @[ FSTTestFieldValue(@"string"), FieldValue::FromString("string").Wrap() ], @[ FSTTestFieldValue(@"strin") ], @[ FSTTestFieldValue(@"e\u0301b") ], // latin small letter e + combining acute accent @[ FSTTestFieldValue(@"\u00e9a") ], // latin small letter e with acute accent diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm index c3215086081..b9d3af60ff3 100644 --- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm +++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm @@ -476,7 +476,7 @@ - (void)testDecodesMutationResult { commitVersion:commitVersion]; XCTAssertEqual(result.version, updateVersion); - XCTAssertEqualObjects(result.transformResults, @[ [FSTStringValue stringValue:@"result"] ]); + XCTAssertEqualObjects(result.transformResults, @[ FieldValue::FromString("result").Wrap() ]); } - (void)testDecodesDeleteMutationResult { diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm index 31172475494..a460b560227 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm +++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm @@ -39,6 +39,7 @@ #include "Firestore/core/src/firebase/firestore/auth/user.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "Firestore/core/src/firebase/firestore/model/types.h" #include "Firestore/core/src/firebase/firestore/remote/existence_filter.h" @@ -59,6 +60,7 @@ using firebase::firestore::core::DocumentViewChange; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::ResourcePath; using firebase::firestore::model::SnapshotVersion; using firebase::firestore::model::TargetId; using firebase::firestore::remote::ExistenceFilter; @@ -170,7 +172,10 @@ - (nullable FSTQuery *)parseQuery:(id)querySpec { } else if ([querySpec isKindOfClass:[NSDictionary class]]) { NSDictionary *queryDict = (NSDictionary *)querySpec; NSString *path = queryDict[@"path"]; - __block FSTQuery *query = FSTTestQuery(util::MakeString(path)); + ResourcePath resource_path = ResourcePath::FromString(util::MakeString(path)); + NSString *_Nullable collectionGroup = queryDict[@"collectionGroup"]; + __block FSTQuery *query = [FSTQuery queryWithPath:resource_path + collectionGroup:collectionGroup]; if (queryDict[@"limit"]) { NSNumber *limit = queryDict[@"limit"]; query = [query queryBySettingLimit:limit.integerValue]; @@ -723,7 +728,7 @@ - (void)testSpecTests { NSBundle *bundle = [NSBundle bundleForClass:[self class]]; NSFileManager *fs = [NSFileManager defaultManager]; BOOL exclusiveMode = NO; - for (NSString *file in [fs enumeratorAtPath:[bundle bundlePath]]) { + for (NSString *file in [fs enumeratorAtPath:[bundle resourcePath]]) { if (![@"json" isEqual:[file pathExtension]]) { continue; } diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm index 768a58202a5..c6737d8ba97 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm +++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm @@ -47,17 +47,20 @@ #include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/log.h" +#include "Firestore/core/src/firebase/firestore/util/objc_compatibility.h" #include "Firestore/core/src/firebase/firestore/util/status.h" #include "Firestore/core/src/firebase/firestore/util/statusor.h" #include "Firestore/core/src/firebase/firestore/util/string_format.h" #include "Firestore/core/src/firebase/firestore/util/to_string.h" #include "absl/memory/memory.h" +namespace objc = firebase::firestore::util::objc; using firebase::firestore::FirestoreErrorCode; using firebase::firestore::auth::EmptyCredentialsProvider; using firebase::firestore::auth::HashUser; using firebase::firestore::auth::User; using firebase::firestore::core::DatabaseInfo; +using firebase::firestore::core::ListenOptions; using firebase::firestore::core::ViewSnapshot; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::DocumentKey; @@ -120,9 +123,6 @@ @interface FSTSyncEngineTestDriver () @property(nonatomic, strong, readonly) void (^eventHandler)(FSTQueryEvent *); /** The events received by our eventHandler and not yet retrieved via capturedEventsSinceLastCall */ @property(nonatomic, strong, readonly) NSMutableArray *events; -/** A dictionary for tracking the listens on queries. */ -@property(nonatomic, strong, readonly) - NSMutableDictionary *queryListeners; #pragma mark - Data structures for holding events sent by the write stream. @@ -144,6 +144,9 @@ @implementation FSTSyncEngineTestDriver { std::unordered_map *, HashUser> _outstandingWrites; DocumentKeySet _expectedLimboDocuments; + /** A dictionary for tracking the listens on queries. */ + objc::unordered_map> _queryListeners; + DatabaseInfo _databaseInfo; User _currentUser; EmptyCredentialsProvider _credentialProvider; @@ -199,8 +202,6 @@ - (instancetype)initWithPersistence:(id)persistence }; _events = events; - _queryListeners = [NSMutableDictionary dictionary]; - _currentUser = initialUser; _acknowledgedDocs = [NSMutableArray array]; @@ -349,13 +350,9 @@ - (FSTOutstandingWrite *)receiveWriteError:(int)errorCode - (TargetId)addUserListenerWithQuery:(FSTQuery *)query { // TODO(dimond): Allow customizing listen options in spec tests // TODO(dimond): Change spec tests to verify isFromCache on snapshots - FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES - includeDocumentMetadataChanges:YES - waitForSyncWhenOnline:NO]; - FSTQueryListener *listener = [[FSTQueryListener alloc] - initWithQuery:query - options:options - viewSnapshotHandler:[self, query](const StatusOr &maybe_snapshot) { + ListenOptions options = ListenOptions::FromIncludeMetadataChanges(true); + auto listener = QueryListener::Create( + query, options, [self, query](const StatusOr &maybe_snapshot) { FSTQueryEvent *event = [[FSTQueryEvent alloc] init]; event.query = query; if (maybe_snapshot.ok()) { @@ -365,17 +362,21 @@ - (TargetId)addUserListenerWithQuery:(FSTQuery *)query { } [self.events addObject:event]; - }]; - self.queryListeners[query] = listener; + }); + _queryListeners[query] = listener; TargetId targetID; _workerQueue->EnqueueBlocking([&] { targetID = [self.eventManager addListener:listener]; }); return targetID; } - (void)removeUserListenerWithQuery:(FSTQuery *)query { - FSTQueryListener *listener = self.queryListeners[query]; - [self.queryListeners removeObjectForKey:query]; - _workerQueue->EnqueueBlocking([&] { [self.eventManager removeListener:listener]; }); + auto found_iter = _queryListeners.find(query); + if (found_iter != _queryListeners.end()) { + std::shared_ptr listener = found_iter->second; + _queryListeners.erase(found_iter); + + _workerQueue->EnqueueBlocking([&] { [self.eventManager removeListener:listener]; }); + } } - (void)writeUserMutation:(FSTMutation *)mutation { @@ -413,7 +414,7 @@ - (void)receiveWatchStreamError:(int)errorCode userInfo:(NSDictionaryEnqueueBlocking([&] { _datastore->FailWatchStream(error); // Unlike web, stream should re-open synchronously (if we have any listeners) - if (self.queryListeners.count > 0) { + if (!_queryListeners.empty()) { HARD_ASSERT(_datastore->IsWatchStreamOpen(), "Watch stream is open"); } }); diff --git a/Firestore/Example/Tests/SpecTests/json/query_spec_test.json b/Firestore/Example/Tests/SpecTests/json/query_spec_test.json new file mode 100644 index 00000000000..4ca89f24cba --- /dev/null +++ b/Firestore/Example/Tests/SpecTests/json/query_spec_test.json @@ -0,0 +1,1170 @@ +{ + "Collection Group query": { + "describeName": "Queries:", + "itName": "Collection Group query", + "tags": [], + "config": { + "useGarbageCollection": true, + "numClients": 1 + }, + "steps": [ + { + "userListen": [ + 2, + { + "path": "cg/1", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "cg/1", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000, + "targetIds": [] + }, + "expect": [ + { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "added": [ + { + "key": "cg/1", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userListen": [ + 4, + { + "path": "cg/2", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "cg/2", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 4 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "cg/2", + "version": 1000, + "value": { + "val": 2 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000, + "targetIds": [] + }, + "expect": [ + { + "query": { + "path": "cg/2", + "filters": [], + "orderBys": [] + }, + "added": [ + { + "key": "cg/2", + "version": 1000, + "value": { + "val": 2 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userListen": [ + 6, + { + "path": "not-cg/nope/cg/3", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "cg/2", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "6": { + "query": { + "path": "not-cg/nope/cg/3", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 6 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "not-cg/nope/cg/3", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "targets": [ + 6 + ] + } + }, + { + "watchCurrent": [ + [ + 6 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000, + "targetIds": [] + }, + "expect": [ + { + "query": { + "path": "not-cg/nope/cg/3", + "filters": [], + "orderBys": [] + }, + "added": [ + { + "key": "not-cg/nope/cg/3", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userListen": [ + 8, + { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "cg/2", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "6": { + "query": { + "path": "not-cg/nope/cg/3", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "8": { + "query": { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 8 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "not-cg/nope", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "targets": [ + 8 + ] + } + }, + { + "watchCurrent": [ + [ + 8 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000, + "targetIds": [] + }, + "expect": [ + { + "query": { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + }, + "added": [ + { + "key": "not-cg/nope", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userListen": [ + 10, + { + "path": "cg/1/not-cg/nope", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "cg/2", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "6": { + "query": { + "path": "not-cg/nope/cg/3", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "8": { + "query": { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "10": { + "query": { + "path": "cg/1/not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 10 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "cg/1/not-cg/nope", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "targets": [ + 10 + ] + } + }, + { + "watchCurrent": [ + [ + 10 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000, + "targetIds": [] + }, + "expect": [ + { + "query": { + "path": "cg/1/not-cg/nope", + "filters": [], + "orderBys": [] + }, + "added": [ + { + "key": "cg/1/not-cg/nope", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userListen": [ + 12, + { + "path": "", + "collectionGroup": "cg", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "cg/2", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "6": { + "query": { + "path": "not-cg/nope/cg/3", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "8": { + "query": { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "10": { + "query": { + "path": "cg/1/not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "12": { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + }, + "expect": [ + { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [], + "orderBys": [] + }, + "added": [ + { + "key": "cg/1", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + }, + { + "key": "cg/2", + "version": 1000, + "value": { + "val": 2 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + }, + { + "key": "not-cg/nope/cg/3", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false + } + ] + }, + { + "userListen": [ + 14, + { + "path": "", + "collectionGroup": "cg", + "filters": [ + [ + "val", + "==", + 1 + ] + ], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "cg/2", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "6": { + "query": { + "path": "not-cg/nope/cg/3", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "8": { + "query": { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "10": { + "query": { + "path": "cg/1/not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "12": { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "14": { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [ + [ + "val", + "==", + 1 + ] + ], + "orderBys": [] + }, + "resumeToken": "" + } + } + }, + "expect": [ + { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [ + [ + "val", + "==", + 1 + ] + ], + "orderBys": [] + }, + "added": [ + { + "key": "cg/1", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + }, + { + "key": "not-cg/nope/cg/3", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false + } + ] + } + ] + }, + "Collection Group query with mutations": { + "describeName": "Queries:", + "itName": "Collection Group query with mutations", + "tags": [], + "config": { + "useGarbageCollection": true, + "numClients": 1 + }, + "steps": [ + { + "userListen": [ + 2, + { + "path": "cg/1", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "cg/1", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000, + "targetIds": [] + }, + "expect": [ + { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "added": [ + { + "key": "cg/1", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userListen": [ + 4, + { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 4 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "not-cg/nope", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000, + "targetIds": [] + }, + "expect": [ + { + "query": { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + }, + "added": [ + { + "key": "not-cg/nope", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userSet": [ + "cg/2", + { + "val": 2 + } + ] + }, + { + "userSet": [ + "not-cg/nope/cg/3", + { + "val": 1 + } + ] + }, + { + "userSet": [ + "not-cg2/nope", + { + "val": 1 + } + ] + }, + { + "userListen": [ + 6, + { + "path": "", + "collectionGroup": "cg", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "6": { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + }, + "expect": [ + { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [], + "orderBys": [] + }, + "added": [ + { + "key": "cg/1", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + }, + { + "key": "cg/2", + "version": 0, + "value": { + "val": 2 + }, + "options": { + "hasLocalMutations": true, + "hasCommittedMutations": false + } + }, + { + "key": "not-cg/nope/cg/3", + "version": 0, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": true, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true + } + ] + }, + { + "userListen": [ + 8, + { + "path": "", + "collectionGroup": "cg", + "filters": [ + [ + "val", + "==", + 1 + ] + ], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "cg/1", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "not-cg/nope", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "6": { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "8": { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [ + [ + "val", + "==", + 1 + ] + ], + "orderBys": [] + }, + "resumeToken": "" + } + } + }, + "expect": [ + { + "query": { + "path": "", + "collectionGroup": "cg", + "filters": [ + [ + "val", + "==", + 1 + ] + ], + "orderBys": [] + }, + "added": [ + { + "key": "cg/1", + "version": 1000, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": false, + "hasCommittedMutations": false + } + }, + { + "key": "not-cg/nope/cg/3", + "version": 0, + "value": { + "val": 1 + }, + "options": { + "hasLocalMutations": true, + "hasCommittedMutations": false + } + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true + } + ] + } + ] + } +} diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h index 1209b6a50e4..a56b9b7eac1 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.h +++ b/Firestore/Example/Tests/Util/FSTHelpers.h @@ -24,6 +24,7 @@ #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" #include "Firestore/core/src/firebase/firestore/model/document_map.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" #include "Firestore/core/src/firebase/firestore/model/field_value.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" @@ -37,7 +38,6 @@ @class FSTDeletedDocument; @class FSTDocument; @class FSTDocumentKeyReference; -@class FSTDocumentSet; @class FSTFieldValue; @class FSTFilter; @class FSTLocalViewChanges; @@ -82,9 +82,9 @@ NS_ASSUME_NONNULL_BEGIN NSComparisonResult result = [left compare:right]; \ NSComparisonResult inverseResult = [right compare:left]; \ XCTAssertEqual(result, expected, @"comparing %@ with %@ at (%lu, %lu)", left, right, \ - i, j); \ + (unsigned long)i, (unsigned long)j); \ XCTAssertEqual(inverseResult, -expected, @"comparing %@ with %@ at (%lu, %lu)", right, \ - left, j, i); \ + left, (unsigned long)j, (unsigned long)i); \ } \ } \ } \ @@ -275,10 +275,11 @@ FSTSortOrder *FSTTestOrderBy(const absl::string_view field, NSString *direction) NSComparator FSTTestDocComparator(const absl::string_view fieldPath); /** - * Creates a FSTDocumentSet based on the given comparator, initially containing the given + * Creates a DocumentSet based on the given comparator, initially containing the given * documents. */ -FSTDocumentSet *FSTTestDocSet(NSComparator comp, NSArray *docs); +firebase::firestore::model::DocumentSet FSTTestDocSet(NSComparator comp, + NSArray *docs); /** Computes changes to the view with the docs and then applies them and returns the snapshot. */ absl::optional FSTTestApplyChanges( diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm index 12facfbfcc2..fcffe6b8ca2 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.mm +++ b/Firestore/Example/Tests/Util/FSTHelpers.mm @@ -32,14 +32,15 @@ #import "Firestore/Source/Local/FSTLocalViewChanges.h" #import "Firestore/Source/Local/FSTQueryData.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Source/Model/FSTFieldValue.h" #import "Firestore/Source/Model/FSTMutation.h" +#include "Firestore/core/src/firebase/firestore/core/filter.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/model/field_mask.h" #include "Firestore/core/src/firebase/firestore/model/field_transform.h" #include "Firestore/core/src/firebase/firestore/model/field_value.h" @@ -54,11 +55,13 @@ namespace testutil = firebase::firestore::testutil; namespace util = firebase::firestore::util; +using firebase::firestore::core::Filter; using firebase::firestore::core::ParsedUpdateData; using firebase::firestore::core::ViewSnapshot; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentSet; using firebase::firestore::model::FieldMask; using firebase::firestore::model::FieldPath; using firebase::firestore::model::FieldTransform; @@ -146,7 +149,7 @@ FSTObjectValue *FSTTestObjectValue(NSDictionary *data) { FSTFieldValue *wrapped = FSTTestFieldValue(data); - HARD_ASSERT([wrapped isKindOfClass:[FSTObjectValue class]], "Unsupported value: %s", data); + HARD_ASSERT(wrapped.type == FieldValue::Type::Object, "Unsupported value: %s", data); return (FSTObjectValue *)wrapped; } @@ -194,19 +197,19 @@ DocumentKey FSTTestDocKey(NSString *path) { FSTFilter *FSTTestFilter(const absl::string_view field, NSString *opString, id value) { const FieldPath path = testutil::Field(field); - FSTRelationFilterOperator op; + Filter::Operator op; if ([opString isEqualToString:@"<"]) { - op = FSTRelationFilterOperatorLessThan; + op = Filter::Operator::LessThan; } else if ([opString isEqualToString:@"<="]) { - op = FSTRelationFilterOperatorLessThanOrEqual; + op = Filter::Operator::LessThanOrEqual; } else if ([opString isEqualToString:@"=="]) { - op = FSTRelationFilterOperatorEqual; + op = Filter::Operator::Equal; } else if ([opString isEqualToString:@">="]) { - op = FSTRelationFilterOperatorGreaterThanOrEqual; + op = Filter::Operator::GreaterThanOrEqual; } else if ([opString isEqualToString:@">"]) { - op = FSTRelationFilterOperatorGreaterThan; + op = Filter::Operator::GreaterThan; } else if ([opString isEqualToString:@"array_contains"]) { - op = FSTRelationFilterOperatorArrayContains; + op = Filter::Operator::ArrayContains; } else { HARD_FAIL("Unsupported operator type: %s", opString); } @@ -236,10 +239,10 @@ NSComparator FSTTestDocComparator(const absl::string_view fieldPath) { return [query comparator]; } -FSTDocumentSet *FSTTestDocSet(NSComparator comp, NSArray *docs) { - FSTDocumentSet *docSet = [FSTDocumentSet documentSetWithComparator:comp]; +DocumentSet FSTTestDocSet(NSComparator comp, NSArray *docs) { + DocumentSet docSet{comp}; for (FSTDocument *doc in docs) { - docSet = [docSet documentSetByAddingDocument:doc]; + docSet = docSet.insert(doc); } return docSet; } diff --git a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm index cd08ec3756d..d05cb9cae9a 100644 --- a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm +++ b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm @@ -85,6 +85,7 @@ - (void)setUp { [super setUp]; [self clearPersistence]; + [self primeBackend]; _firestores = [NSMutableArray array]; self.db = [self firestore]; @@ -193,7 +194,7 @@ - (FIRFirestore *)firestoreWithProjectID:(NSString *)projectID { FIRFirestore *firestore = [[FIRFirestore alloc] initWithProjectID:util::MakeString(projectID) database:DatabaseId::kDefault - persistenceKey:persistenceKey + persistenceKey:util::MakeString(persistenceKey) credentialsProvider:std::move(credentials_provider) workerQueue:std::move(workerQueue) firebaseApp:app]; @@ -202,14 +203,13 @@ - (FIRFirestore *)firestoreWithProjectID:(NSString *)projectID { [_firestores addObject:firestore]; - [self primeBackend:firestore]; - return firestore; } -- (void)primeBackend:(FIRFirestore *)db { +- (void)primeBackend { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ + FIRFirestore *db = [self firestore]; XCTestExpectation *watchInitialized = [self expectationWithDescription:@"Prime backend: Watch initialized"]; __block XCTestExpectation *watchUpdateReceived; @@ -247,6 +247,8 @@ - (void)primeBackend:(FIRFirestore *)db { }]; [listenerRegistration remove]; + + [self shutdownFirestore:db]; }); } @@ -393,14 +395,13 @@ - (void)mergeDocumentRef:(FIRDocumentReference *)ref } - (void)disableNetwork { - [self.db.client + [self.db disableNetworkWithCompletion:[self completionForExpectationWithName:@"Disable Network."]]; [self awaitExpectations]; } - (void)enableNetwork { - [self.db.client - enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable Network."]]; + [self.db enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable Network."]]; [self awaitExpectations]; } diff --git a/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj b/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj index 4a1e9d48833..f88531043f1 100644 --- a/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj +++ b/Firestore/Protos/FrameworkMaker.xcodeproj/project.pbxproj @@ -288,7 +288,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -327,7 +327,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/Firestore/Source/API/FIRCollectionReference.mm b/Firestore/Source/API/FIRCollectionReference.mm index 76f441359eb..9f2bdc2ee0f 100644 --- a/Firestore/Source/API/FIRCollectionReference.mm +++ b/Firestore/Source/API/FIRCollectionReference.mm @@ -15,21 +15,24 @@ */ #import "FIRCollectionReference.h" -#import "FIRFirestore.h" + +#include #include "Firestore/core/src/firebase/firestore/util/autoid.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRQuery+Internal.h" #import "Firestore/Source/API/FIRQuery_Init.h" #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::ResourcePath; using firebase::firestore::util::CreateAutoId; @@ -56,9 +59,9 @@ @implementation FIRCollectionReference - (instancetype)initWithPath:(const ResourcePath &)path firestore:(FIRFirestore *)firestore { if (path.size() % 2 != 1) { - FSTThrowInvalidArgument(@"Invalid collection reference. Collection references must have an odd " - "number of segments, but %s has %zu", - path.CanonicalString().c_str(), path.size()); + ThrowInvalidArgument("Invalid collection reference. Collection references must have an odd " + "number of segments, but %s has %s", + path.CanonicalString(), path.size()); } self = [super initWithQuery:[FSTQuery queryWithPath:path] firestore:firestore]; return self; @@ -99,7 +102,8 @@ - (FIRDocumentReference *_Nullable)parent { return nil; } else { DocumentKey key{parentPath}; - return [FIRDocumentReference referenceWithKey:key firestore:self.firestore]; + return [[FIRDocumentReference alloc] initWithKey:std::move(key) + firestore:self.firestore.wrapped]; } } @@ -109,11 +113,12 @@ - (NSString *)path { - (FIRDocumentReference *)documentWithPath:(NSString *)documentPath { if (!documentPath) { - FSTThrowInvalidArgument(@"Document path cannot be nil."); + ThrowInvalidArgument("Document path cannot be nil."); } const ResourcePath subPath = ResourcePath::FromString(util::MakeString(documentPath)); - const ResourcePath path = self.query.path.Append(subPath); - return [FIRDocumentReference referenceWithPath:path firestore:self.firestore]; + ResourcePath path = self.query.path.Append(subPath); + return [[FIRDocumentReference alloc] initWithPath:std::move(path) + firestore:self.firestore.wrapped]; } - (FIRDocumentReference *)addDocumentWithData:(NSDictionary *)data { @@ -129,8 +134,8 @@ - (FIRDocumentReference *)addDocumentWithData:(NSDictionary *)da } - (FIRDocumentReference *)documentWithAutoID { - const DocumentKey key{self.query.path.Append(CreateAutoId())}; - return [FIRDocumentReference referenceWithKey:key firestore:self.firestore]; + DocumentKey key{self.query.path.Append(CreateAutoId())}; + return [[FIRDocumentReference alloc] initWithKey:std::move(key) firestore:self.firestore.wrapped]; } @end diff --git a/Firestore/Source/API/FIRDocumentChange+Internal.h b/Firestore/Source/API/FIRDocumentChange+Internal.h index 220a3683a42..6c79f543ec1 100644 --- a/Firestore/Source/API/FIRDocumentChange+Internal.h +++ b/Firestore/Source/API/FIRDocumentChange+Internal.h @@ -16,20 +16,17 @@ #import "FIRDocumentChange.h" -#include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#import -@class FIRFirestore; +#include "Firestore/core/src/firebase/firestore/api/document_change.h" + +using firebase::firestore::api::DocumentChange; NS_ASSUME_NONNULL_BEGIN -/** Internal FIRDocumentChange API we don't want exposed in our public header files. */ -@interface FIRDocumentChange (Internal) +@interface FIRDocumentChange (/* Init */) -/** Calculates the array of FIRDocumentChange's based on the given FSTViewSnapshot. */ -+ (NSArray *)documentChangesForSnapshot: - (const firebase::firestore::core::ViewSnapshot &)snapshot - includeMetadataChanges:(BOOL)includeMetadataChanges - firestore:(FIRFirestore *)firestore; +- (instancetype)initWithDocumentChange:(DocumentChange &&)documentChange NS_DESIGNATED_INITIALIZER; @end diff --git a/Firestore/Source/API/FIRDocumentChange.mm b/Firestore/Source/API/FIRDocumentChange.mm index b793faa3402..7a561c44dd3 100644 --- a/Firestore/Source/API/FIRDocumentChange.mm +++ b/Firestore/Source/API/FIRDocumentChange.mm @@ -14,125 +14,24 @@ * limitations under the License. */ -#import "FIRDocumentChange.h" +#import "Firestore/Source/API/FIRDocumentChange+Internal.h" #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" -#import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" -#include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/api/document_change.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" -using firebase::firestore::core::DocumentViewChange; -using firebase::firestore::core::ViewSnapshot; +using firebase::firestore::api::DocumentChange; NS_ASSUME_NONNULL_BEGIN -@interface FIRDocumentChange () - -- (instancetype)initWithType:(FIRDocumentChangeType)type - document:(FIRDocumentSnapshot *)document - oldIndex:(NSUInteger)oldIndex - newIndex:(NSUInteger)newIndex NS_DESIGNATED_INITIALIZER; - -@end - -@implementation FIRDocumentChange (Internal) - -+ (FIRDocumentChangeType)documentChangeTypeForChange:(const DocumentViewChange &)change { - switch (change.type()) { - case DocumentViewChange::Type::kAdded: - return FIRDocumentChangeTypeAdded; - case DocumentViewChange::Type::kModified: - case DocumentViewChange::Type::kMetadata: - return FIRDocumentChangeTypeModified; - case DocumentViewChange::Type::kRemoved: - return FIRDocumentChangeTypeRemoved; - } - - HARD_FAIL("Unknown DocumentViewChange::Type: %s", change.type()); +@implementation FIRDocumentChange { + DocumentChange _documentChange; } -+ (NSArray *)documentChangesForSnapshot:(const ViewSnapshot &)snapshot - includeMetadataChanges:(BOOL)includeMetadataChanges - firestore:(FIRFirestore *)firestore { - if (snapshot.old_documents().isEmpty) { - // Special case the first snapshot because index calculation is easy and fast. Also all changes - // on the first snapshot are adds so there are also no metadata-only changes to filter out. - FSTDocument *_Nullable lastDocument = nil; - NSUInteger index = 0; - NSMutableArray *changes = [NSMutableArray array]; - for (const DocumentViewChange &change : snapshot.document_changes()) { - FIRQueryDocumentSnapshot *document = [FIRQueryDocumentSnapshot - snapshotWithFirestore:firestore - documentKey:change.document().key - document:change.document() - fromCache:snapshot.from_cache() - hasPendingWrites:snapshot.mutated_keys().contains(change.document().key)]; - HARD_ASSERT(change.type() == DocumentViewChange::Type::kAdded, - "Invalid event type for first snapshot"); - HARD_ASSERT(!lastDocument || snapshot.query().comparator(lastDocument, change.document()) == - NSOrderedAscending, - "Got added events in wrong order"); - [changes addObject:[[FIRDocumentChange alloc] initWithType:FIRDocumentChangeTypeAdded - document:document - oldIndex:NSNotFound - newIndex:index++]]; - } - return changes; - } else { - // A DocumentSet that is updated incrementally as changes are applied to use to lookup the index - // of a document. - FSTDocumentSet *indexTracker = snapshot.old_documents(); - NSMutableArray *changes = [NSMutableArray array]; - for (const DocumentViewChange &change : snapshot.document_changes()) { - if (!includeMetadataChanges && change.type() == DocumentViewChange::Type::kMetadata) { - continue; - } - - FIRQueryDocumentSnapshot *document = [FIRQueryDocumentSnapshot - snapshotWithFirestore:firestore - documentKey:change.document().key - document:change.document() - fromCache:snapshot.from_cache() - hasPendingWrites:snapshot.mutated_keys().contains(change.document().key)]; - - NSUInteger oldIndex = NSNotFound; - NSUInteger newIndex = NSNotFound; - if (change.type() != DocumentViewChange::Type::kAdded) { - oldIndex = [indexTracker indexOfKey:change.document().key]; - HARD_ASSERT(oldIndex != NSNotFound, "Index for document not found"); - indexTracker = [indexTracker documentSetByRemovingKey:change.document().key]; - } - if (change.type() != DocumentViewChange::Type::kRemoved) { - indexTracker = [indexTracker documentSetByAddingDocument:change.document()]; - newIndex = [indexTracker indexOfKey:change.document().key]; - } - [FIRDocumentChange documentChangeTypeForChange:change]; - FIRDocumentChangeType type = [FIRDocumentChange documentChangeTypeForChange:change]; - [changes addObject:[[FIRDocumentChange alloc] initWithType:type - document:document - oldIndex:oldIndex - newIndex:newIndex]]; - } - return changes; - } -} - -@end - -@implementation FIRDocumentChange - -- (instancetype)initWithType:(FIRDocumentChangeType)type - document:(FIRQueryDocumentSnapshot *)document - oldIndex:(NSUInteger)oldIndex - newIndex:(NSUInteger)newIndex { +- (instancetype)initWithDocumentChange:(DocumentChange &&)documentChange { if (self = [super init]) { - _type = type; - _document = document; - _oldIndex = oldIndex; - _newIndex = newIndex; + _documentChange = std::move(documentChange); } return self; } @@ -142,16 +41,36 @@ - (BOOL)isEqual:(nullable id)other { if (![other isKindOfClass:[FIRDocumentChange class]]) return NO; FIRDocumentChange *change = (FIRDocumentChange *)other; - return self.type == change.type && [self.document isEqual:change.document] && - self.oldIndex == change.oldIndex && self.newIndex == change.newIndex; + return _documentChange == change->_documentChange; } - (NSUInteger)hash { - NSUInteger result = (NSUInteger)self.type; - result = result * 31u + [self.document hash]; - result = result * 31u + (NSUInteger)self.oldIndex; - result = result * 31u + (NSUInteger)self.newIndex; - return result; + return _documentChange.Hash(); +} + +- (FIRDocumentChangeType)type { + switch (_documentChange.type()) { + case DocumentChange::Type::Added: + return FIRDocumentChangeTypeAdded; + case DocumentChange::Type::Modified: + return FIRDocumentChangeTypeModified; + case DocumentChange::Type::Removed: + return FIRDocumentChangeTypeRemoved; + } + + HARD_FAIL("Unknown DocumentChange::Type: %s", _documentChange.type()); +} + +- (FIRQueryDocumentSnapshot *)document { + return [[FIRQueryDocumentSnapshot alloc] initWithSnapshot:_documentChange.document()]; +} + +- (NSUInteger)oldIndex { + return _documentChange.old_index(); +} + +- (NSUInteger)newIndex { + return _documentChange.new_index(); } @end diff --git a/Firestore/Source/API/FIRDocumentReference+Internal.h b/Firestore/Source/API/FIRDocumentReference+Internal.h index eb078ca535d..aa12e97f784 100644 --- a/Firestore/Source/API/FIRDocumentReference+Internal.h +++ b/Firestore/Source/API/FIRDocumentReference+Internal.h @@ -16,19 +16,28 @@ #import "FIRDocumentReference.h" +#include "Firestore/core/src/firebase/firestore/api/document_reference.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" NS_ASSUME_NONNULL_BEGIN +@interface FIRDocumentReference (/* Init */) + +- (instancetype)initWithReference:(firebase::firestore::api::DocumentReference &&)reference + NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithPath:(firebase::firestore::model::ResourcePath)path + firestore:(firebase::firestore::api::Firestore *)firestore; + +- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key + firestore:(firebase::firestore::api::Firestore *)firestore; + +@end + /** Internal FIRDocumentReference API we don't want exposed in our public header files. */ @interface FIRDocumentReference (Internal) -+ (instancetype)referenceWithPath:(const firebase::firestore::model::ResourcePath &)path - firestore:(FIRFirestore *)firestore; -+ (instancetype)referenceWithKey:(firebase::firestore::model::DocumentKey)key - firestore:(FIRFirestore *)firestore; - - (const firebase::firestore::model::DocumentKey &)key; @end diff --git a/Firestore/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm index f4c3a616878..84c9a2b9ac4 100644 --- a/Firestore/Source/API/FIRDocumentReference.mm +++ b/Firestore/Source/API/FIRDocumentReference.mm @@ -20,41 +20,54 @@ #include #import "FIRFirestoreErrors.h" -#import "FIRFirestoreSource.h" #import "Firestore/Source/API/FIRCollectionReference+Internal.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" +#import "Firestore/Source/API/FIRFirestoreSource+Internal.h" #import "Firestore/Source/API/FIRListenerRegistration+Internal.h" #import "Firestore/Source/API/FSTUserDataConverter.h" #import "Firestore/Source/Core/FSTEventManager.h" #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Source/Model/FSTFieldValue.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" #include "Firestore/core/src/firebase/firestore/api/document_reference.h" +#include "Firestore/core/src/firebase/firestore/api/document_snapshot.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" +#include "Firestore/core/src/firebase/firestore/api/source.h" +#include "Firestore/core/src/firebase/firestore/core/event_listener.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/model/precondition.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/util/error_apple.h" +#include "Firestore/core/src/firebase/firestore/util/status.h" +#include "Firestore/core/src/firebase/firestore/util/statusor.h" +#include "Firestore/core/src/firebase/firestore/util/statusor_callback.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; using firebase::firestore::api::DocumentReference; +using firebase::firestore::api::DocumentSnapshot; +using firebase::firestore::api::Firestore; +using firebase::firestore::api::Source; +using firebase::firestore::api::MakeSource; +using firebase::firestore::api::ThrowInvalidArgument; +using firebase::firestore::core::EventListener; +using firebase::firestore::core::ListenOptions; using firebase::firestore::core::ParsedSetData; using firebase::firestore::core::ParsedUpdateData; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::Precondition; using firebase::firestore::model::ResourcePath; +using firebase::firestore::util::Status; +using firebase::firestore::util::StatusOr; +using firebase::firestore::util::StatusOrCallback; NS_ASSUME_NONNULL_BEGIN #pragma mark - FIRDocumentReference -@interface FIRDocumentReference () -- (instancetype)initWithReference:(DocumentReference &&)reference NS_DESIGNATED_INITIALIZER; -@end - @implementation FIRDocumentReference { DocumentReference _documentReference; } @@ -66,6 +79,20 @@ - (instancetype)initWithReference:(DocumentReference &&)reference { return self; } +- (instancetype)initWithPath:(ResourcePath)path firestore:(Firestore *)firestore { + if (path.size() % 2 != 0) { + ThrowInvalidArgument("Invalid document reference. Document references must have an even " + "number of segments, but %s has %s", + path.CanonicalString(), path.size()); + } + return [self initWithKey:DocumentKey{std::move(path)} firestore:firestore]; +} + +- (instancetype)initWithKey:(DocumentKey)key firestore:(Firestore *)firestore { + DocumentReference delegate{std::move(key), firestore}; + return [self initWithReference:std::move(delegate)]; +} + #pragma mark - NSObject Methods - (BOOL)isEqual:(nullable id)other { @@ -84,7 +111,7 @@ - (NSUInteger)hash { @dynamic firestore; - (FIRFirestore *)firestore { - return _documentReference.firestore(); + return [FIRFirestore recoverFromFirestore:_documentReference.firestore()]; } - (NSString *)documentID { @@ -92,7 +119,8 @@ - (NSString *)documentID { } - (FIRCollectionReference *)parent { - return _documentReference.Parent(); + return [FIRCollectionReference referenceWithPath:_documentReference.key().path().PopLast() + firestore:self.firestore]; } - (NSString *)path { @@ -101,9 +129,12 @@ - (NSString *)path { - (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath { if (!collectionPath) { - FSTThrowInvalidArgument(@"Collection path cannot be nil."); + ThrowInvalidArgument("Collection path cannot be nil."); } - return _documentReference.GetCollectionReference(util::MakeString(collectionPath)); + + ResourcePath subPath = ResourcePath::FromString(util::MakeString(collectionPath)); + ResourcePath path = _documentReference.key().path().Append(subPath); + return [FIRCollectionReference referenceWithPath:path firestore:self.firestore]; } - (void)setData:(NSDictionary *)documentData { @@ -127,10 +158,9 @@ - (void)setData:(NSDictionary *)documentData - (void)setData:(NSDictionary *)documentData merge:(BOOL)merge completion:(nullable void (^)(NSError *_Nullable error))completion { - ParsedSetData parsed = - merge ? [_documentReference.firestore().dataConverter parsedMergeData:documentData - fieldMask:nil] - : [_documentReference.firestore().dataConverter parsedSetData:documentData]; + auto dataConverter = self.firestore.dataConverter; + ParsedSetData parsed = merge ? [dataConverter parsedMergeData:documentData fieldMask:nil] + : [dataConverter parsedSetData:documentData]; _documentReference.SetData( std::move(parsed).ToMutations(_documentReference.key(), Precondition::None()), completion); } @@ -138,8 +168,8 @@ - (void)setData:(NSDictionary *)documentData - (void)setData:(NSDictionary *)documentData mergeFields:(NSArray *)mergeFields completion:(nullable void (^)(NSError *_Nullable error))completion { - ParsedSetData parsed = [_documentReference.firestore().dataConverter parsedMergeData:documentData - fieldMask:mergeFields]; + ParsedSetData parsed = [self.firestore.dataConverter parsedMergeData:documentData + fieldMask:mergeFields]; _documentReference.SetData( std::move(parsed).ToMutations(_documentReference.key(), Precondition::None()), completion); } @@ -150,7 +180,7 @@ - (void)updateData:(NSDictionary *)fields { - (void)updateData:(NSDictionary *)fields completion:(nullable void (^)(NSError *_Nullable error))completion { - ParsedUpdateData parsed = [_documentReference.firestore().dataConverter parsedUpdateData:fields]; + ParsedUpdateData parsed = [self.firestore.dataConverter parsedUpdateData:fields]; _documentReference.UpdateData( std::move(parsed).ToMutations(_documentReference.key(), Precondition::Exists(true)), completion); @@ -164,15 +194,13 @@ - (void)deleteDocumentWithCompletion:(nullable void (^)(NSError *_Nullable error _documentReference.DeleteDocument(completion); } -- (void)getDocumentWithCompletion:(void (^)(FIRDocumentSnapshot *_Nullable document, - NSError *_Nullable error))completion { - [self getDocumentWithSource:FIRFirestoreSourceDefault completion:completion]; +- (void)getDocumentWithCompletion:(FIRDocumentSnapshotBlock)completion { + _documentReference.GetDocument(Source::Default, [self wrapDocumentSnapshotBlock:completion]); } - (void)getDocumentWithSource:(FIRFirestoreSource)source - completion:(void (^)(FIRDocumentSnapshot *_Nullable document, - NSError *_Nullable error))completion { - _documentReference.GetDocument(source, completion); + completion:(FIRDocumentSnapshotBlock)completion { + _documentReference.GetDocument(MakeSource(source), [self wrapDocumentSnapshotBlock:completion]); } - (id)addSnapshotListener:(FIRDocumentSnapshotBlock)listener { @@ -182,22 +210,38 @@ - (void)getDocumentWithSource:(FIRFirestoreSource)source - (id) addSnapshotListenerWithIncludeMetadataChanges:(BOOL)includeMetadataChanges listener:(FIRDocumentSnapshotBlock)listener { - FSTListenOptions *options = - [self internalOptionsForIncludeMetadataChanges:includeMetadataChanges]; + ListenOptions options = ListenOptions::FromIncludeMetadataChanges(includeMetadataChanges); return [self addSnapshotListenerInternalWithOptions:options listener:listener]; } -- (id) - addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions - listener:(FIRDocumentSnapshotBlock)listener { - return _documentReference.AddSnapshotListener(listener, internalOptions); +- (id)addSnapshotListenerInternalWithOptions:(ListenOptions)internalOptions + listener:(FIRDocumentSnapshotBlock) + listener { + ListenerRegistration result = _documentReference.AddSnapshotListener( + std::move(internalOptions), [self wrapDocumentSnapshotBlock:listener]); + return [[FSTListenerRegistration alloc] initWithRegistration:std::move(result)]; } -/** Converts the public API options object to the internal options object. */ -- (FSTListenOptions *)internalOptionsForIncludeMetadataChanges:(BOOL)includeMetadataChanges { - return [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:includeMetadataChanges - includeDocumentMetadataChanges:includeMetadataChanges - waitForSyncWhenOnline:NO]; +- (DocumentSnapshot::Listener)wrapDocumentSnapshotBlock:(FIRDocumentSnapshotBlock)block { + class Converter : public EventListener { + public: + explicit Converter(FIRDocumentSnapshotBlock block) : block_(block) { + } + + void OnEvent(StatusOr maybe_snapshot) override { + if (maybe_snapshot.ok()) { + FIRDocumentSnapshot *result = + [[FIRDocumentSnapshot alloc] initWithSnapshot:std::move(maybe_snapshot).ValueOrDie()]; + block_(result, nil); + } else { + block_(nil, util::MakeNSError(maybe_snapshot.status())); + } + } + + private: + FIRDocumentSnapshotBlock block_; + }; + return absl::make_unique(block); } @end @@ -206,20 +250,6 @@ - (FSTListenOptions *)internalOptionsForIncludeMetadataChanges:(BOOL)includeMeta @implementation FIRDocumentReference (Internal) -+ (instancetype)referenceWithPath:(const ResourcePath &)path firestore:(FIRFirestore *)firestore { - if (path.size() % 2 != 0) { - FSTThrowInvalidArgument(@"Invalid document reference. Document references must have an even " - "number of segments, but %s has %zu", - path.CanonicalString().c_str(), path.size()); - } - return [FIRDocumentReference referenceWithKey:DocumentKey{path} firestore:firestore]; -} - -+ (instancetype)referenceWithKey:(DocumentKey)key firestore:(FIRFirestore *)firestore { - DocumentReference underlyingReference{firestore, std::move(key)}; - return [[FIRDocumentReference alloc] initWithReference:std::move(underlyingReference)]; -} - - (const DocumentKey &)key { return _documentReference.key(); } diff --git a/Firestore/Source/API/FIRDocumentSnapshot+Internal.h b/Firestore/Source/API/FIRDocumentSnapshot+Internal.h index fdc5ac8a3d5..b371b6a03cc 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot+Internal.h +++ b/Firestore/Source/API/FIRDocumentSnapshot+Internal.h @@ -16,22 +16,40 @@ #import "FIRDocumentSnapshot.h" +#include "Firestore/core/src/firebase/firestore/api/document_snapshot.h" +#include "Firestore/core/src/firebase/firestore/api/snapshot_metadata.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" @class FIRFirestore; @class FSTDocument; +using firebase::firestore::api::DocumentSnapshot; +using firebase::firestore::api::Firestore; +using firebase::firestore::api::SnapshotMetadata; +using firebase::firestore::model::DocumentKey; + NS_ASSUME_NONNULL_BEGIN +@interface FIRDocumentSnapshot (/* Init */) + +- (instancetype)initWithSnapshot:(DocumentSnapshot &&)snapshot NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithFirestore:(Firestore *)firestore + documentKey:(DocumentKey)documentKey + document:(nullable FSTDocument *)document + metadata:(SnapshotMetadata)metadata; + +- (instancetype)initWithFirestore:(Firestore *)firestore + documentKey:(DocumentKey)documentKey + document:(nullable FSTDocument *)document + fromCache:(bool)fromCache + hasPendingWrites:(bool)hasPendingWrites; + +@end + /** Internal FIRDocumentSnapshot API we don't want exposed in our public header files. */ @interface FIRDocumentSnapshot (Internal) -+ (instancetype)snapshotWithFirestore:(FIRFirestore *)firestore - documentKey:(firebase::firestore::model::DocumentKey)documentKey - document:(nullable FSTDocument *)document - fromCache:(BOOL)fromCache - hasPendingWrites:(BOOL)pendingWrites; - @property(nonatomic, strong, readonly, nullable) FSTDocument *internalDocument; @end diff --git a/Firestore/Source/API/FIRDocumentSnapshot.mm b/Firestore/Source/API/FIRDocumentSnapshot.mm index 3cc684e5046..ea9eb9d422a 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot.mm +++ b/Firestore/Source/API/FIRDocumentSnapshot.mm @@ -20,7 +20,6 @@ #include "Firestore/core/src/firebase/firestore/util/warnings.h" -#import "FIRFirestore.h" #import "FIRFirestoreSettings.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" @@ -29,18 +28,23 @@ #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTFieldValue.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" #include "Firestore/core/src/firebase/firestore/api/document_snapshot.h" +#include "Firestore/core/src/firebase/firestore/api/firestore.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; using firebase::firestore::api::DocumentSnapshot; +using firebase::firestore::api::Firestore; +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::FieldValue; using firebase::firestore::util::WrapNSString; NS_ASSUME_NONNULL_BEGIN @@ -65,29 +69,10 @@ ServerTimestampBehavior InternalServerTimestampBehavior(FIRServerTimestampBehavi } // namespace -@interface FIRDocumentSnapshot () - -- (instancetype)initWithSnapshot:(DocumentSnapshot &&)snapshot; - -@end - -@implementation FIRDocumentSnapshot (Internal) - -+ (instancetype)snapshotWithFirestore:(FIRFirestore *)firestore - documentKey:(DocumentKey)documentKey - document:(nullable FSTDocument *)document - fromCache:(BOOL)fromCache - hasPendingWrites:(BOOL)pendingWrites { - DocumentSnapshot underlyingSnapshot{firestore, documentKey, document, - static_cast(fromCache), - static_cast(pendingWrites)}; - return [[[self class] alloc] initWithSnapshot:std::move(underlyingSnapshot)]; -} - -@end - @implementation FIRDocumentSnapshot { DocumentSnapshot _snapshot; + + FIRSnapshotMetadata *_cachedMetadata; } - (instancetype)initWithSnapshot:(DocumentSnapshot &&)snapshot { @@ -97,6 +82,25 @@ - (instancetype)initWithSnapshot:(DocumentSnapshot &&)snapshot { return self; } +- (instancetype)initWithFirestore:(Firestore *)firestore + documentKey:(DocumentKey)documentKey + document:(nullable FSTDocument *)document + metadata:(SnapshotMetadata)metadata { + DocumentSnapshot wrapped{firestore, std::move(documentKey), document, std::move(metadata)}; + return [self initWithSnapshot:std::move(wrapped)]; +} + +- (instancetype)initWithFirestore:(Firestore *)firestore + documentKey:(DocumentKey)documentKey + document:(nullable FSTDocument *)document + fromCache:(bool)fromCache + hasPendingWrites:(bool)hasPendingWrites { + return [self initWithFirestore:firestore + documentKey:std::move(documentKey) + document:document + metadata:SnapshotMetadata(hasPendingWrites, fromCache)]; +} + // NSObject Methods - (BOOL)isEqual:(nullable id)other { if (other == self) return YES; @@ -121,7 +125,7 @@ - (FSTDocument *)internalDocument { } - (FIRDocumentReference *)reference { - return _snapshot.CreateReference(); + return [[FIRDocumentReference alloc] initWithReference:_snapshot.CreateReference()]; } - (NSString *)documentID { @@ -131,7 +135,10 @@ - (NSString *)documentID { @dynamic metadata; - (FIRSnapshotMetadata *)metadata { - return _snapshot.GetMetadata(); + if (!_cachedMetadata) { + _cachedMetadata = [[FIRSnapshotMetadata alloc] initWithMetadata:_snapshot.metadata()]; + } + return _cachedMetadata; } - (nullable NSDictionary *)data { @@ -157,7 +164,7 @@ - (nullable id)valueForField:(id)field } else if ([field isKindOfClass:[FIRFieldPath class]]) { fieldPath = field; } else { - FSTThrowInvalidArgument(@"Subscript key must be an NSString or FIRFieldPath."); + ThrowInvalidArgument("Subscript key must be an NSString or FIRFieldPath."); } FSTFieldValue *fieldValue = _snapshot.GetValue(fieldPath.internalValue); @@ -174,19 +181,21 @@ - (FSTFieldValueOptions *)optionsForServerTimestampBehavior: SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN() return [[FSTFieldValueOptions alloc] initWithServerTimestampBehavior:InternalServerTimestampBehavior(serverTimestampBehavior) - timestampsInSnapshotsEnabled:_snapshot.firestore().settings.timestampsInSnapshotsEnabled]; + timestampsInSnapshotsEnabled:_snapshot.firestore() + ->settings() + .timestampsInSnapshotsEnabled]; SUPPRESS_END() } - (id)convertedValue:(FSTFieldValue *)value options:(FSTFieldValueOptions *)options { - if ([value isKindOfClass:[FSTObjectValue class]]) { + if (value.type == FieldValue::Type::Object) { return [self convertedObject:(FSTObjectValue *)value options:options]; - } else if ([value isKindOfClass:[FSTArrayValue class]]) { + } else if (value.type == FieldValue::Type::Array) { return [self convertedArray:(FSTArrayValue *)value options:options]; - } else if ([value isKindOfClass:[FSTReferenceValue class]]) { + } else if (value.type == FieldValue::Type::Reference) { FSTReferenceValue *ref = (FSTReferenceValue *)value; const DatabaseId *refDatabase = ref.databaseID; - const DatabaseId *database = _snapshot.firestore().databaseID; + const DatabaseId *database = &_snapshot.firestore()->database_id(); if (*refDatabase != *database) { // TODO(b/32073923): Log this as a proper warning. NSLog(@"WARNING: Document %@ contains a document reference within a different database " @@ -197,7 +206,7 @@ - (id)convertedValue:(FSTFieldValue *)value options:(FSTFieldValueOptions *)opti database->database_id().c_str()); } DocumentKey key = [[ref valueWithOptions:options] key]; - return [FIRDocumentReference referenceWithKey:key firestore:_snapshot.firestore()]; + return [[FIRDocumentReference alloc] initWithKey:key firestore:_snapshot.firestore()]; } else { return [value valueWithOptions:options]; } @@ -225,18 +234,8 @@ - (id)convertedValue:(FSTFieldValue *)value options:(FSTFieldValueOptions *)opti @end -@interface FIRQueryDocumentSnapshot () - -- (instancetype)initWithSnapshot:(DocumentSnapshot &&)snapshot NS_DESIGNATED_INITIALIZER; - -@end - @implementation FIRQueryDocumentSnapshot -- (instancetype)initWithSnapshot:(DocumentSnapshot &&)snapshot { - return [super initWithSnapshot:std::move(snapshot)]; -} - - (NSDictionary *)data { NSDictionary *data = [super data]; HARD_ASSERT(data, "Document in a QueryDocumentSnapshot should exist"); diff --git a/Firestore/Source/API/FIRFieldPath.mm b/Firestore/Source/API/FIRFieldPath.mm index 68f3de105b7..cdf67196e6b 100644 --- a/Firestore/Source/API/FIRFieldPath.mm +++ b/Firestore/Source/API/FIRFieldPath.mm @@ -22,13 +22,14 @@ #include #import "Firestore/Source/API/FIRFieldPath+Internal.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" #include "Firestore/core/src/firebase/firestore/util/hashing.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::model::FieldPath; NS_ASSUME_NONNULL_BEGIN @@ -44,19 +45,16 @@ @implementation FIRFieldPath - (instancetype)initWithFields:(NSArray *)fieldNames { if (fieldNames.count == 0) { - FSTThrowInvalidArgument(@"Invalid field path. Provided names must not be empty."); + ThrowInvalidArgument("Invalid field path. Provided names must not be empty."); } std::vector field_names; field_names.reserve(fieldNames.count); for (int i = 0; i < fieldNames.count; ++i) { - if (fieldNames[i].length == 0) { - FSTThrowInvalidArgument(@"Invalid field name at index %d. Field names must not be empty.", i); - } field_names.emplace_back(util::MakeString(fieldNames[i])); } - return [self initPrivate:FieldPath(std::move(field_names))]; + return [self initPrivate:FieldPath::FromSegments(std::move(field_names))]; } + (instancetype)documentID { @@ -71,21 +69,8 @@ - (instancetype)initPrivate:(FieldPath)fieldPath { } + (instancetype)pathWithDotSeparatedString:(NSString *)path { - if ([[FIRFieldPath reservedCharactersRegex] - numberOfMatchesInString:path - options:0 - range:NSMakeRange(0, path.length)] > 0) { - FSTThrowInvalidArgument( - @"Invalid field path (%@). Paths must not contain '~', '*', '/', '[', or ']'", path); - } - @try { - return [[FIRFieldPath alloc] initWithFields:[path componentsSeparatedByString:@"."]]; - } @catch (NSException *exception) { - FSTThrowInvalidArgument( - @"Invalid field path (%@). Paths must not be empty, begin with '.', end with '.', or " - @"contain '..'", - path); - } + return + [[FIRFieldPath alloc] initPrivate:FieldPath::FromDotSeparatedString(util::MakeString(path))]; } /** Matches any characters in a field path string that are reserved. */ diff --git a/Firestore/Source/API/FIRFirestore+Internal.h b/Firestore/Source/API/FIRFirestore+Internal.h index 0878237316a..2b2174fba2c 100644 --- a/Firestore/Source/API/FIRFirestore+Internal.h +++ b/Firestore/Source/API/FIRFirestore+Internal.h @@ -19,13 +19,13 @@ #include #include +#include "Firestore/core/src/firebase/firestore/api/firestore.h" #include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" -#include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/util/async_queue.h" -#include "absl/strings/string_view.h" NS_ASSUME_NONNULL_BEGIN +@class FIRApp; @class FSTFirestoreClient; @class FSTUserDataConverter; @@ -38,12 +38,11 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype) initWithProjectID:(std::string)projectID database:(std::string)database - persistenceKey:(NSString *)persistenceKey + persistenceKey:(std::string)persistenceKey credentialsProvider: (std::unique_ptr)credentialsProvider workerQueue:(std::unique_ptr)workerQueue firebaseApp:(FIRApp *)app; - @end /** Internal FIRFirestore API we don't want exposed in our public header files. */ @@ -52,6 +51,8 @@ NS_ASSUME_NONNULL_BEGIN /** Checks to see if logging is is globally enabled for the Firestore client. */ + (BOOL)isLoggingEnabled; ++ (FIRFirestore *)recoverFromFirestore:(firebase::firestore::api::Firestore *)firestore; + /** * Shutdown this `FIRFirestore`, releasing all resources (abandoning any outstanding writes, * removing all listens, closing all network connections, etc.). @@ -61,11 +62,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion NS_SWIFT_NAME(shutdown(completion:)); -- (firebase::firestore::util::AsyncQueue *)workerQueue; +@property(nonatomic, assign, readonly) firebase::firestore::api::Firestore *wrapped; + +@property(nonatomic, assign, readonly) firebase::firestore::util::AsyncQueue *workerQueue; // FIRFirestore ownes the DatabaseId instance. @property(nonatomic, assign, readonly) const firebase::firestore::model::DatabaseId *databaseID; -@property(nonatomic, strong, readonly) FSTFirestoreClient *client; @property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter; @end diff --git a/Firestore/Source/API/FIRFirestore.mm b/Firestore/Source/API/FIRFirestore.mm index 70771bf8305..fb0a5551d81 100644 --- a/Firestore/Source/API/FIRFirestore.mm +++ b/Firestore/Source/API/FIRFirestore.mm @@ -26,73 +26,45 @@ #include #include -#import "FIRFirestoreSettings.h" -#import "Firestore/Source/API/FIRCollectionReference+Internal.h" +#import "FIRFirestore.h" + #import "Firestore/Source/API/FIRDocumentReference+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" -#import "Firestore/Source/API/FIRTransaction+Internal.h" -#import "Firestore/Source/API/FIRWriteBatch+Internal.h" #import "Firestore/Source/API/FSTFirestoreComponent.h" #import "Firestore/Source/API/FSTUserDataConverter.h" -#import "Firestore/Source/Core/FSTFirestoreClient.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/firestore.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" -#include "Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h" #include "Firestore/core/src/firebase/firestore/core/database_info.h" -#include "Firestore/core/src/firebase/firestore/core/transaction.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" -#include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/util/async_queue.h" -#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h" -#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "Firestore/core/src/firebase/firestore/util/delayed_constructor.h" #include "Firestore/core/src/firebase/firestore/util/log.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" -#include "absl/memory/memory.h" namespace util = firebase::firestore::util; +using firebase::firestore::api::DocumentReference; +using firebase::firestore::api::Firestore; +using firebase::firestore::api::ThrowIllegalState; +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::auth::CredentialsProvider; -using firebase::firestore::auth::FirebaseCredentialsProvider; -using firebase::firestore::core::DatabaseInfo; -using firebase::firestore::core::Transaction; using firebase::firestore::model::DatabaseId; -using firebase::firestore::model::ResourcePath; -using util::AsyncQueue; -using util::Executor; -using util::ExecutorLibdispatch; +using firebase::firestore::util::AsyncQueue; +using firebase::firestore::util::DelayedConstructor; NS_ASSUME_NONNULL_BEGIN -extern "C" NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain"; - #pragma mark - FIRFirestore -@interface FIRFirestore () { - /** The actual owned DatabaseId instance is allocated in FIRFirestore. */ - DatabaseId _databaseID; - std::unique_ptr _credentialsProvider; -} - -@property(nonatomic, strong) NSString *persistenceKey; +@interface FIRFirestore () -// Note that `client` is updated after initialization, but marking this readwrite would generate an -// incorrect setter (since we make the assignment to `client` inside an `@synchronized` block. -@property(nonatomic, strong, readonly) FSTFirestoreClient *client; @property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter; @end @implementation FIRFirestore { - // Ownership will be transferred to `FSTFirestoreClient` as soon as the client is created. - std::unique_ptr _workerQueue; - - // All guarded by @synchronized(self) - FIRFirestoreSettings *_settings; - FSTFirestoreClient *_client; -} - -- (AsyncQueue *)workerQueue { - return [_client workerQueue]; + DelayedConstructor _firestore; } + (NSMutableDictionary *)instances { @@ -137,9 +109,8 @@ + (void)initialize { + (instancetype)firestore { FIRApp *app = [FIRApp defaultApp]; if (!app) { - FSTThrowInvalidUsage(@"FIRAppNotConfiguredException", - @"Failed to get FirebaseApp instance. Please call FirebaseApp.configure() " - @"before using Firestore"); + ThrowIllegalState("Failed to get FirebaseApp instance. Please call FirebaseApp.configure() " + "before using Firestore"); } return [self firestoreForApp:app database:util::WrapNSString(DatabaseId::kDefault)]; } @@ -151,13 +122,13 @@ + (instancetype)firestoreForApp:(FIRApp *)app { // TODO(b/62410906): make this public + (instancetype)firestoreForApp:(FIRApp *)app database:(NSString *)database { if (!app) { - FSTThrowInvalidArgument(@"FirebaseApp instance may not be nil. Use FirebaseApp.app() if you'd " - "like to use the default FirebaseApp instance."); + ThrowInvalidArgument("FirebaseApp instance may not be nil. Use FirebaseApp.app() if you'd like " + "to use the default FirebaseApp instance."); } if (!database) { - FSTThrowInvalidArgument(@"database identifier may not be nil. Use '%s' if you want the default " - "database", - DatabaseId::kDefault); + ThrowInvalidArgument("Database identifier may not be nil. Use '%s' if you want the default " + "database", + DatabaseId::kDefault); } id provider = @@ -167,12 +138,16 @@ + (instancetype)firestoreForApp:(FIRApp *)app database:(NSString *)database { - (instancetype)initWithProjectID:(std::string)projectID database:(std::string)database - persistenceKey:(NSString *)persistenceKey + persistenceKey:(std::string)persistenceKey credentialsProvider:(std::unique_ptr)credentialsProvider workerQueue:(std::unique_ptr)workerQueue firebaseApp:(FIRApp *)app { if (self = [super init]) { - _databaseID = DatabaseId{std::move(projectID), std::move(database)}; + _firestore.Init(std::move(projectID), std::move(database), std::move(persistenceKey), + std::move(credentialsProvider), std::move(workerQueue), (__bridge void *)self); + + _app = app; + FSTPreConverterBlock block = ^id _Nullable(id _Nullable input) { if ([input isKindOfClass:[FIRDocumentReference class]]) { FIRDocumentReference *documentReference = (FIRDocumentReference *)input; @@ -182,94 +157,58 @@ - (instancetype)initWithProjectID:(std::string)projectID return input; } }; - _dataConverter = [[FSTUserDataConverter alloc] initWithDatabaseID:&_databaseID + + _dataConverter = [[FSTUserDataConverter alloc] initWithDatabaseID:&_firestore->database_id() preConverter:block]; - _persistenceKey = persistenceKey; - _credentialsProvider = std::move(credentialsProvider); - _workerQueue = std::move(workerQueue); - _app = app; - _settings = [[FIRFirestoreSettings alloc] init]; } return self; } - (FIRFirestoreSettings *)settings { - @synchronized(self) { - // Disallow mutation of our internal settings - return [_settings copy]; - } + return _firestore->settings(); } - (void)setSettings:(FIRFirestoreSettings *)settings { - @synchronized(self) { - // As a special exception, don't throw if the same settings are passed repeatedly. This should - // make it more friendly to create a Firestore instance. - if (_client && ![_settings isEqual:settings]) { - FSTThrowInvalidUsage(@"FIRIllegalStateException", - @"Firestore instance has already been started and its settings can no " - "longer be changed. You can only set settings before calling any " - "other methods on a Firestore instance."); - } - _settings = [settings copy]; - } -} - -/** - * Ensures that the FirestoreClient is configured and returns it. - */ -- (FSTFirestoreClient *)client { - [self ensureClientConfigured]; - return _client; -} - -- (void)ensureClientConfigured { - @synchronized(self) { - if (!_client) { - // These values are validated elsewhere; this is just double-checking: - HARD_ASSERT(_settings.host, "FirestoreSettings.host cannot be nil."); - HARD_ASSERT(_settings.dispatchQueue, "FirestoreSettings.dispatchQueue cannot be nil."); - - const DatabaseInfo database_info(*self.databaseID, util::MakeString(_persistenceKey), - util::MakeString(_settings.host), _settings.sslEnabled); - - std::unique_ptr userExecutor = - absl::make_unique(_settings.dispatchQueue); - - HARD_ASSERT(_workerQueue, "Expected non-null _workerQueue"); - _client = [FSTFirestoreClient clientWithDatabaseInfo:database_info - settings:_settings - credentialsProvider:_credentialsProvider.get() - userExecutor:std::move(userExecutor) - workerQueue:std::move(_workerQueue)]; - } - } + _firestore->set_settings(settings); } - (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath { if (!collectionPath) { - FSTThrowInvalidArgument(@"Collection path cannot be nil."); + ThrowInvalidArgument("Collection path cannot be nil."); } if ([collectionPath containsString:@"//"]) { - FSTThrowInvalidArgument(@"Invalid path (%@). Paths must not contain // in them.", - collectionPath); + ThrowInvalidArgument("Invalid path (%s). Paths must not contain // in them.", collectionPath); } - [self ensureClientConfigured]; - const ResourcePath path = ResourcePath::FromString(util::MakeString(collectionPath)); - return [FIRCollectionReference referenceWithPath:path firestore:self]; + return _firestore->GetCollection(util::MakeString(collectionPath)); } - (FIRDocumentReference *)documentWithPath:(NSString *)documentPath { if (!documentPath) { - FSTThrowInvalidArgument(@"Document path cannot be nil."); + ThrowInvalidArgument("Document path cannot be nil."); } if ([documentPath containsString:@"//"]) { - FSTThrowInvalidArgument(@"Invalid path (%@). Paths must not contain // in them.", documentPath); + ThrowInvalidArgument("Invalid path (%s). Paths must not contain // in them.", documentPath); } - [self ensureClientConfigured]; - const ResourcePath path = ResourcePath::FromString(util::MakeString(documentPath)); - return [FIRDocumentReference referenceWithPath:path firestore:self]; + DocumentReference documentReference = _firestore->GetDocument(util::MakeString(documentPath)); + return [[FIRDocumentReference alloc] initWithReference:std::move(documentReference)]; +} + +- (FIRQuery *)collectionGroupWithID:(NSString *)collectionID { + if (!collectionID) { + ThrowInvalidArgument("Collection ID cannot be nil."); + } + if ([collectionID containsString:@"/"]) { + ThrowInvalidArgument("Invalid collection ID (%s). Collection IDs must not contain / in them.", + collectionID); + } + + return _firestore->GetCollectionGroup(collectionID); +} + +- (FIRWriteBatch *)batch { + return _firestore->GetBatch(); } - (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **))updateBlock @@ -279,34 +218,12 @@ - (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **)) // We wrap the function they provide in order to use internal implementation classes for // transaction, and to run the user callback block on the proper queue. if (!updateBlock) { - FSTThrowInvalidArgument(@"Transaction block cannot be nil."); + ThrowInvalidArgument("Transaction block cannot be nil."); } else if (!completion) { - FSTThrowInvalidArgument(@"Transaction completion block cannot be nil."); + ThrowInvalidArgument("Transaction completion block cannot be nil."); } - FSTTransactionBlock wrappedUpdate = - ^(std::shared_ptr internalTransaction, - void (^internalCompletion)(id _Nullable, NSError *_Nullable)) { - FIRTransaction *transaction = - [FIRTransaction transactionWithInternalTransaction:std::move(internalTransaction) - firestore:self]; - dispatch_async(queue, ^{ - NSError *_Nullable error = nil; - id _Nullable result = updateBlock(transaction, &error); - if (error) { - // Force the result to be nil in the case of an error, in case the user set both. - result = nil; - } - internalCompletion(result, error); - }); - }; - [self.client transactionWithRetries:5 updateBlock:wrappedUpdate completion:completion]; -} - -- (FIRWriteBatch *)batch { - [self ensureClientConfigured]; - - return [FIRWriteBatch writeBatchWithFirestore:self]; + _firestore->RunTransaction(updateBlock, queue, completion); } - (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **error))updateBlock @@ -323,38 +240,44 @@ - (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **er completion:completion]; } -- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion { - if (!_client) { - if (completion) { - // We should be dispatching the callback on the user dispatch queue but if the client is nil - // here that queue was never created. - completion(nil); - } - } else { - [_client shutdownWithCompletion:completion]; - } -} - -+ (BOOL)isLoggingEnabled { - return FIRIsLoggableLevel(FIRLoggerLevelDebug, NO); -} - + (void)enableLogging:(BOOL)logging { FIRSetLoggerLevel(logging ? FIRLoggerLevelDebug : FIRLoggerLevelNotice); } - (void)enableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion { - [self ensureClientConfigured]; - [self.client enableNetworkWithCompletion:completion]; + _firestore->EnableNetwork(completion); } - (void)disableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable))completion { - [self ensureClientConfigured]; - [self.client disableNetworkWithCompletion:completion]; + _firestore->DisableNetwork(completion); +} + +@end + +@implementation FIRFirestore (Internal) + +- (Firestore *)wrapped { + return _firestore.get(); +} + +- (AsyncQueue *)workerQueue { + return _firestore->worker_queue(); } - (const DatabaseId *)databaseID { - return &_databaseID; + return &_firestore->database_id(); +} + ++ (BOOL)isLoggingEnabled { + return FIRIsLoggableLevel(FIRLoggerLevelDebug, NO); +} + ++ (FIRFirestore *)recoverFromFirestore:(Firestore *)firestore { + return (__bridge FIRFirestore *)firestore->extension(); +} + +- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion { + _firestore->Shutdown(completion); } @end diff --git a/Firestore/Source/API/FIRFirestoreSettings.mm b/Firestore/Source/API/FIRFirestoreSettings.mm index 3dd7dbda29b..5ddf569a63a 100644 --- a/Firestore/Source/API/FIRFirestoreSettings.mm +++ b/Firestore/Source/API/FIRFirestoreSettings.mm @@ -14,14 +14,15 @@ * limitations under the License. */ -#include "Firestore/core/src/firebase/firestore/util/warnings.h" - #import "FIRFirestoreSettings.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" +#include "Firestore/core/src/firebase/firestore/util/warnings.h" NS_ASSUME_NONNULL_BEGIN +using firebase::firestore::api::ThrowInvalidArgument; + static NSString *const kDefaultHost = @"firestore.googleapis.com"; static const BOOL kDefaultSSLEnabled = YES; static const BOOL kDefaultPersistenceEnabled = YES; @@ -88,20 +89,19 @@ - (id)copyWithZone:(nullable NSZone *)zone { - (void)setHost:(NSString *)host { if (!host) { - FSTThrowInvalidArgument( - @"host setting may not be nil. You should generally just use the default value " - "(which is %@)", - kDefaultHost); + ThrowInvalidArgument("Host setting may not be nil. You should generally just use the default " + "value (which is %s)", + kDefaultHost); } _host = [host mutableCopy]; } - (void)setDispatchQueue:(dispatch_queue_t)dispatchQueue { if (!dispatchQueue) { - FSTThrowInvalidArgument( - @"dispatch queue setting may not be nil. Create a new dispatch queue with " - "dispatch_queue_create(\"com.example.MyQueue\", NULL) or just use the default " - "(which is the main queue, returned from dispatch_get_main_queue())"); + ThrowInvalidArgument( + "Dispatch queue setting may not be nil. Create a new dispatch queue with " + "dispatch_queue_create(\"com.example.MyQueue\", NULL) or just use the default (which is " + "the main queue, returned from dispatch_get_main_queue())"); } _dispatchQueue = dispatchQueue; } @@ -109,7 +109,7 @@ - (void)setDispatchQueue:(dispatch_queue_t)dispatchQueue { - (void)setCacheSizeBytes:(int64_t)cacheSizeBytes { if (cacheSizeBytes != kFIRFirestoreCacheSizeUnlimited && cacheSizeBytes < kMinimumCacheSizeBytes) { - FSTThrowInvalidArgument(@"Cache size must be set to at least %i bytes", kMinimumCacheSizeBytes); + ThrowInvalidArgument("Cache size must be set to at least %s bytes", kMinimumCacheSizeBytes); } _cacheSizeBytes = cacheSizeBytes; } diff --git a/Firestore/Source/API/FIRFirestoreSource+Internal.h b/Firestore/Source/API/FIRFirestoreSource+Internal.h new file mode 100644 index 00000000000..694a7de0b99 --- /dev/null +++ b/Firestore/Source/API/FIRFirestoreSource+Internal.h @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRFirestoreSource.h" + +#include "Firestore/core/src/firebase/firestore/api/source.h" + +namespace firebase { +namespace firestore { +namespace api { + +Source MakeSource(FIRFirestoreSource source); + +} // namespace api +} // namespace firestore +} // namespace firebase diff --git a/Firestore/Source/API/FIRFirestoreSource.mm b/Firestore/Source/API/FIRFirestoreSource.mm new file mode 100644 index 00000000000..9d3444a7a89 --- /dev/null +++ b/Firestore/Source/API/FIRFirestoreSource.mm @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "Firestore/Source/API/FIRFirestoreSource+Internal.h" + +#include "Firestore/core/src/firebase/firestore/api/source.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" + +namespace firebase { +namespace firestore { +namespace api { + +Source MakeSource(FIRFirestoreSource source) { + switch (source) { + case FIRFirestoreSourceDefault: + return Source::Default; + case FIRFirestoreSourceServer: + return Source::Server; + case FIRFirestoreSourceCache: + return Source::Cache; + } + + UNREACHABLE(); +} + +} // namespace api +} // namespace firestore +} // namespace firebase diff --git a/Firestore/Source/API/FIRGeoPoint.mm b/Firestore/Source/API/FIRGeoPoint.mm index cfd70fb5507..8e35d539806 100644 --- a/Firestore/Source/API/FIRGeoPoint.mm +++ b/Firestore/Source/API/FIRGeoPoint.mm @@ -16,10 +16,10 @@ #import "Firestore/Source/API/FIRGeoPoint+Internal.h" -#import "Firestore/core/src/firebase/firestore/util/comparison.h" - -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" +#include "Firestore/core/src/firebase/firestore/util/comparison.h" +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::util::DoubleBitwiseEquals; using firebase::firestore::util::DoubleBitwiseHash; using firebase::firestore::util::WrapCompare; @@ -31,14 +31,14 @@ @implementation FIRGeoPoint - (instancetype)initWithLatitude:(double)latitude longitude:(double)longitude { if (self = [super init]) { if (latitude < -90 || latitude > 90 || !isfinite(latitude)) { - FSTThrowInvalidArgument(@"GeoPoint requires a latitude value in the range of [-90, 90], " - "but was %f", - latitude); + ThrowInvalidArgument("GeoPoint requires a latitude value in the range of [-90, 90], " + "but was %s", + latitude); } if (longitude < -180 || longitude > 180 || !isfinite(longitude)) { - FSTThrowInvalidArgument(@"GeoPoint requires a longitude value in the range of [-180, 180], " - "but was %f", - longitude); + ThrowInvalidArgument("GeoPoint requires a longitude value in the range of [-180, 180], " + "but was %s", + longitude); } _latitude = latitude; diff --git a/Firestore/Source/API/FIRListenerRegistration+Internal.h b/Firestore/Source/API/FIRListenerRegistration+Internal.h index 4cd2d579364..e3f1cd59247 100644 --- a/Firestore/Source/API/FIRListenerRegistration+Internal.h +++ b/Firestore/Source/API/FIRListenerRegistration+Internal.h @@ -16,18 +16,16 @@ #import "FIRListenerRegistration.h" -@class FSTAsyncQueryListener; -@class FSTFirestoreClient; -@class FSTQueryListener; +#include "Firestore/core/src/firebase/firestore/api/listener_registration.h" NS_ASSUME_NONNULL_BEGIN +using firebase::firestore::api::ListenerRegistration; + /** Private implementation of the FIRListenerRegistration protocol. */ @interface FSTListenerRegistration : NSObject -- (instancetype)initWithClient:(FSTFirestoreClient *)client - asyncListener:(FSTAsyncQueryListener *)asyncListener - internalListener:(FSTQueryListener *)internalListener; +- (instancetype)initWithRegistration:(ListenerRegistration &&)registration; @end diff --git a/Firestore/Source/API/FIRListenerRegistration.mm b/Firestore/Source/API/FIRListenerRegistration.mm index 3d4d5ccd8e2..b69b9fbdb95 100644 --- a/Firestore/Source/API/FIRListenerRegistration.mm +++ b/Firestore/Source/API/FIRListenerRegistration.mm @@ -14,44 +14,29 @@ * limitations under the License. */ +#include + #import "Firestore/Source/API/FIRListenerRegistration+Internal.h" -#import "Firestore/Source/Core/FSTFirestoreClient.h" -#import "Firestore/Source/Util/FSTAsyncQueryListener.h" +#include "Firestore/core/src/firebase/firestore/util/delayed_constructor.h" NS_ASSUME_NONNULL_BEGIN -@interface FSTListenerRegistration () - -/** The client that was used to register this listen. */ -@property(nonatomic, strong, readonly) FSTFirestoreClient *client; - -/** The async listener that is used to mute events synchronously. */ -@property(nonatomic, strong, readonly, nullable) FSTAsyncQueryListener *asyncListener; - -/** The internal FSTQueryListener that can be used to unlisten the query. */ -@property(nonatomic, strong, readwrite, nullable) FSTQueryListener *internalListener; +using firebase::firestore::util::DelayedConstructor; -@end - -@implementation FSTListenerRegistration +@implementation FSTListenerRegistration { + DelayedConstructor _registration; +} -- (instancetype)initWithClient:(FSTFirestoreClient *)client - asyncListener:(FSTAsyncQueryListener *)asyncListener - internalListener:(FSTQueryListener *)internalListener { +- (instancetype)initWithRegistration:(ListenerRegistration &&)registration { if (self = [super init]) { - _client = client; - _asyncListener = asyncListener; - _internalListener = internalListener; + _registration.Init(std::move(registration)); } return self; } - (void)remove { - [self.asyncListener mute]; - [self.client removeListener:self.internalListener]; - _internalListener = nil; - _asyncListener = nil; + _registration->Remove(); } @end diff --git a/Firestore/Source/API/FIRQuery.mm b/Firestore/Source/API/FIRQuery.mm index c6ef8731644..9b09bc1fc6e 100644 --- a/Firestore/Source/API/FIRQuery.mm +++ b/Firestore/Source/API/FIRQuery.mm @@ -16,16 +16,17 @@ #import "FIRQuery.h" +#include #include #import "FIRDocumentReference.h" #import "FIRFirestoreErrors.h" -#import "FIRFirestoreSource.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" #import "Firestore/Source/API/FIRFieldPath+Internal.h" #import "Firestore/Source/API/FIRFieldValue+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" +#import "Firestore/Source/API/FIRFirestoreSource+Internal.h" #import "Firestore/Source/API/FIRListenerRegistration+Internal.h" #import "Firestore/Source/API/FIRQuery+Internal.h" #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" @@ -37,11 +38,12 @@ #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTFieldValue.h" -#import "Firestore/Source/Util/FSTAsyncQueryListener.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" +#include "Firestore/core/src/firebase/firestore/core/filter.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/util/error_apple.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" @@ -49,10 +51,16 @@ #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; +using firebase::firestore::api::MakeSource; +using firebase::firestore::api::Source; +using firebase::firestore::api::ThrowInvalidArgument; +using firebase::firestore::core::AsyncEventListener; +using firebase::firestore::core::EventListener; +using firebase::firestore::core::Filter; using firebase::firestore::core::ViewSnapshot; -using firebase::firestore::core::ViewSnapshotHandler; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldPath; +using firebase::firestore::model::FieldValue; using firebase::firestore::model::ResourcePath; using firebase::firestore::util::MakeNSError; using firebase::firestore::util::StatusOr; @@ -110,18 +118,19 @@ - (void)getDocumentsWithCompletion:(void (^)(FIRQuerySnapshot *_Nullable snapsho [self getDocumentsWithSource:FIRFirestoreSourceDefault completion:completion]; } -- (void)getDocumentsWithSource:(FIRFirestoreSource)source +- (void)getDocumentsWithSource:(FIRFirestoreSource)publicSource completion:(void (^)(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error))completion { - if (source == FIRFirestoreSourceCache) { - [self.firestore.client getDocumentsFromLocalCache:self completion:completion]; + Source source = MakeSource(publicSource); + if (source == Source::Cache) { + [self.firestore.wrapped->client() getDocumentsFromLocalCache:self completion:completion]; return; } - FSTListenOptions *listenOptions = - [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES - includeDocumentMetadataChanges:YES - waitForSyncWhenOnline:YES]; + ListenOptions listenOptions( + /*include_query_metadata_changes=*/true, + /*include_document_metadata_changes=*/true, + /*wait_for_sync_when_online=*/true); dispatch_semaphore_t registered = dispatch_semaphore_create(0); __block id listenerRegistration; @@ -136,17 +145,16 @@ - (void)getDocumentsWithSource:(FIRFirestoreSource)source dispatch_semaphore_wait(registered, DISPATCH_TIME_FOREVER); [listenerRegistration remove]; - if (snapshot.metadata.fromCache && source == FIRFirestoreSourceServer) { - completion(nil, - [NSError errorWithDomain:FIRFirestoreErrorDomain - code:FIRFirestoreErrorCodeUnavailable - userInfo:@{ - NSLocalizedDescriptionKey : - @"Failed to get documents from server. (However, these " - @"documents may exist in the local cache. Run again " - @"without setting source to FIRFirestoreSourceServer to " - @"retrieve the cached documents.)" - }]); + if (snapshot.metadata.fromCache && source == Source::Server) { + completion(nil, [NSError errorWithDomain:FIRFirestoreErrorDomain + code:FIRFirestoreErrorCodeUnavailable + userInfo:@{ + NSLocalizedDescriptionKey : + @"Failed to get documents from server. (However, these " + @"documents may exist in the local cache. Run again " + @"without setting source to FirestoreSourceServer to " + @"retrieve the cached documents.)" + }]); } else { completion(snapshot, nil); } @@ -164,112 +172,102 @@ - (void)getDocumentsWithSource:(FIRFirestoreSource)source - (id) addSnapshotListenerWithIncludeMetadataChanges:(BOOL)includeMetadataChanges listener:(FIRQuerySnapshotBlock)listener { - auto options = [self internalOptionsForIncludeMetadataChanges:includeMetadataChanges]; + auto options = ListenOptions::FromIncludeMetadataChanges(includeMetadataChanges); return [self addSnapshotListenerInternalWithOptions:options listener:listener]; } -- (id) - addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions - listener:(FIRQuerySnapshotBlock)listener { - FIRFirestore *firestore = self.firestore; +- (id)addSnapshotListenerInternalWithOptions:(ListenOptions)internalOptions + listener: + (FIRQuerySnapshotBlock)listener { + Firestore *firestore = self.firestore.wrapped; FSTQuery *query = self.query; - ViewSnapshotHandler snapshotHandler = [listener, firestore, - query](const StatusOr &maybe_snapshot) { - if (!maybe_snapshot.status().ok()) { - listener(nil, MakeNSError(maybe_snapshot.status())); - return; - } - ViewSnapshot snapshot = maybe_snapshot.ValueOrDie(); + // Convert from ViewSnapshots to QuerySnapshots. + auto view_listener = EventListener::Create( + [listener, firestore, query](StatusOr maybe_snapshot) { + if (!maybe_snapshot.status().ok()) { + listener(nil, MakeNSError(maybe_snapshot.status())); + return; + } - FIRSnapshotMetadata *metadata = - [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:snapshot.has_pending_writes() - fromCache:snapshot.from_cache()]; + ViewSnapshot snapshot = std::move(maybe_snapshot).ValueOrDie(); + SnapshotMetadata metadata(snapshot.has_pending_writes(), snapshot.from_cache()); - listener([FIRQuerySnapshot snapshotWithFirestore:firestore - originalQuery:query - snapshot:std::move(snapshot) - metadata:metadata], - nil); - }; + listener([[FIRQuerySnapshot alloc] initWithFirestore:firestore + originalQuery:query + snapshot:std::move(snapshot) + metadata:std::move(metadata)], + nil); + }); + + // Call the view_listener on the user Executor. + auto async_listener = AsyncEventListener::Create(firestore->client().userExecutor, + std::move(view_listener)); - FSTAsyncQueryListener *asyncListener = - [[FSTAsyncQueryListener alloc] initWithExecutor:self.firestore.client.userExecutor - snapshotHandler:std::move(snapshotHandler)]; + std::shared_ptr query_listener = + [firestore->client() listenToQuery:query options:internalOptions listener:async_listener]; - FSTQueryListener *internalListener = - [firestore.client listenToQuery:query - options:internalOptions - viewSnapshotHandler:[asyncListener asyncSnapshotHandler]]; - return [[FSTListenerRegistration alloc] initWithClient:self.firestore.client - asyncListener:asyncListener - internalListener:internalListener]; + return [[FSTListenerRegistration alloc] + initWithRegistration:ListenerRegistration(firestore->client(), std::move(async_listener), + std::move(query_listener))]; } - (FIRQuery *)queryWhereField:(NSString *)field isEqualTo:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorEqual field:field value:value]; + return [self queryWithFilterOperator:Filter::Operator::Equal field:field value:value]; } - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isEqualTo:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorEqual - path:path.internalValue - value:value]; + return [self queryWithFilterOperator:Filter::Operator::Equal path:path.internalValue value:value]; } - (FIRQuery *)queryWhereField:(NSString *)field isLessThan:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThan field:field value:value]; + return [self queryWithFilterOperator:Filter::Operator::LessThan field:field value:value]; } - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isLessThan:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThan + return [self queryWithFilterOperator:Filter::Operator::LessThan path:path.internalValue value:value]; } - (FIRQuery *)queryWhereField:(NSString *)field isLessThanOrEqualTo:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThanOrEqual - field:field - value:value]; + return [self queryWithFilterOperator:Filter::Operator::LessThanOrEqual field:field value:value]; } - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isLessThanOrEqualTo:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThanOrEqual + return [self queryWithFilterOperator:Filter::Operator::LessThanOrEqual path:path.internalValue value:value]; } - (FIRQuery *)queryWhereField:(NSString *)field isGreaterThan:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThan - field:field - value:value]; + return [self queryWithFilterOperator:Filter::Operator::GreaterThan field:field value:value]; } - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isGreaterThan:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThan + return [self queryWithFilterOperator:Filter::Operator::GreaterThan path:path.internalValue value:value]; } - (FIRQuery *)queryWhereField:(NSString *)field arrayContains:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorArrayContains - field:field - value:value]; + return [self queryWithFilterOperator:Filter::Operator::ArrayContains field:field value:value]; } - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path arrayContains:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorArrayContains + return [self queryWithFilterOperator:Filter::Operator::ArrayContains path:path.internalValue value:value]; } - (FIRQuery *)queryWhereField:(NSString *)field isGreaterThanOrEqualTo:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThanOrEqual + return [self queryWithFilterOperator:Filter::Operator::GreaterThanOrEqual field:field value:value]; } - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isGreaterThanOrEqualTo:(id)value { - return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThanOrEqual + return [self queryWithFilterOperator:Filter::Operator::GreaterThanOrEqual path:path.internalValue value:value]; } @@ -277,7 +275,7 @@ - (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isGreaterThanOrEqualTo:(i - (FIRQuery *)queryFilteredUsingComparisonPredicate:(NSPredicate *)predicate { NSComparisonPredicate *comparison = (NSComparisonPredicate *)predicate; if (comparison.comparisonPredicateModifier != NSDirectPredicateModifier) { - FSTThrowInvalidArgument(@"Invalid query. Predicate cannot have an aggregate modifier."); + ThrowInvalidArgument("Invalid query. Predicate cannot have an aggregate modifier."); } NSString *path; id value = nil; @@ -316,24 +314,24 @@ - (FIRQuery *)queryFilteredUsingComparisonPredicate:(NSPredicate *)predicate { default:; // Fallback below to throw assertion. } } else { - FSTThrowInvalidArgument( - @"Invalid query. Predicate comparisons must include a key path and a constant."); + ThrowInvalidArgument( + "Invalid query. Predicate comparisons must include a key path and a constant."); } // Fallback cases of unsupported comparison operator. switch (comparison.predicateOperatorType) { case NSCustomSelectorPredicateOperatorType: - FSTThrowInvalidArgument(@"Invalid query. Custom predicate filters are not supported."); + ThrowInvalidArgument("Invalid query. Custom predicate filters are not supported."); break; default: - FSTThrowInvalidArgument(@"Invalid query. Operator type %lu is not supported.", - (unsigned long)comparison.predicateOperatorType); + ThrowInvalidArgument("Invalid query. Operator type %s is not supported.", + comparison.predicateOperatorType); } } - (FIRQuery *)queryFilteredUsingCompoundPredicate:(NSPredicate *)predicate { NSCompoundPredicate *compound = (NSCompoundPredicate *)predicate; if (compound.compoundPredicateType != NSAndPredicateType || compound.subpredicates.count == 0) { - FSTThrowInvalidArgument(@"Invalid query. Only compound queries using AND are supported."); + ThrowInvalidArgument("Invalid query. Only compound queries using AND are supported."); } FIRQuery *query = self; for (NSPredicate *pred in compound.subpredicates) { @@ -351,13 +349,11 @@ - (FIRQuery *)queryFilteredUsingPredicate:(NSPredicate *)predicate { predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) { return true; }] class]]) { - FSTThrowInvalidArgument(@"Invalid query. Block-based predicates are not " - "supported. Please use predicateWithFormat to " - "create predicates instead."); + ThrowInvalidArgument("Invalid query. Block-based predicates are not supported. Please use " + "predicateWithFormat to create predicates instead."); } else { - FSTThrowInvalidArgument(@"Invalid query. Expect comparison or compound of " - "comparison predicate. Please use " - "predicateWithFormat to create predicates."); + ThrowInvalidArgument("Invalid query. Expect comparison or compound of comparison predicate. " + "Please use predicateWithFormat to create predicates."); } } @@ -378,14 +374,12 @@ - (FIRQuery *)queryOrderedByField:(NSString *)field descending:(BOOL)descending - (FIRQuery *)queryOrderedByFieldPath:(FIRFieldPath *)fieldPath descending:(BOOL)descending { [self validateNewOrderByPath:fieldPath.internalValue]; if (self.query.startAt) { - FSTThrowInvalidUsage( - @"InvalidQueryException", - @"Invalid query. You must not specify a starting point before specifying the order by."); + ThrowInvalidArgument( + "Invalid query. You must not specify a starting point before specifying the order by."); } if (self.query.endAt) { - FSTThrowInvalidUsage( - @"InvalidQueryException", - @"Invalid query. You must not specify an ending point before specifying the order by."); + ThrowInvalidArgument( + "Invalid query. You must not specify an ending point before specifying the order by."); } FSTSortOrder *sortOrder = [FSTSortOrder sortOrderWithFieldPath:fieldPath.internalValue ascending:!descending]; @@ -395,8 +389,8 @@ - (FIRQuery *)queryOrderedByFieldPath:(FIRFieldPath *)fieldPath descending:(BOOL - (FIRQuery *)queryLimitedTo:(NSInteger)limit { if (limit <= 0) { - FSTThrowInvalidArgument(@"Invalid Query. Query limit (%ld) is invalid. Limit must be positive.", - (long)limit); + ThrowInvalidArgument("Invalid Query. Query limit (%s) is invalid. Limit must be positive.", + limit); } return [FIRQuery referenceWithQuery:[self.query queryBySettingLimit:limit] firestore:_firestore]; } @@ -452,7 +446,7 @@ - (FIRQuery *)queryEndingAtValues:(NSArray *)fieldValues { #pragma mark - Private Methods /** Private helper for all of the queryWhereField: methods. */ -- (FIRQuery *)queryWithFilterOperator:(FSTRelationFilterOperator)filterOperator +- (FIRQuery *)queryWithFilterOperator:(Filter::Operator)filterOperator field:(NSString *)field value:(id)value { return [self queryWithFilterOperator:filterOperator @@ -460,27 +454,34 @@ - (FIRQuery *)queryWithFilterOperator:(FSTRelationFilterOperator)filterOperator value:value]; } -- (FIRQuery *)queryWithFilterOperator:(FSTRelationFilterOperator)filterOperator +- (FIRQuery *)queryWithFilterOperator:(Filter::Operator)filterOperator path:(const FieldPath &)fieldPath value:(id)value { FSTFieldValue *fieldValue; if (fieldPath.IsKeyFieldPath()) { - if (filterOperator == FSTRelationFilterOperatorArrayContains) { - FSTThrowInvalidArgument( - @"Invalid query. You can't perform arrayContains queries on document ID since document " - "IDs are not arrays."); + if (filterOperator == Filter::Operator::ArrayContains) { + ThrowInvalidArgument("Invalid query. You can't perform arrayContains queries on document ID " + "since document IDs are not arrays."); } if ([value isKindOfClass:[NSString class]]) { NSString *documentKey = (NSString *)value; - if ([documentKey containsString:@"/"]) { - FSTThrowInvalidArgument(@"Invalid query. When querying by document ID you must provide " - "a valid document ID, but '%@' contains a '/' character.", - documentKey); - } else if (documentKey.length == 0) { - FSTThrowInvalidArgument(@"Invalid query. When querying by document ID you must provide " - "a valid document ID, but it was an empty string."); + if (documentKey.length == 0) { + ThrowInvalidArgument("Invalid query. When querying by document ID you must provide a valid " + "document ID, but it was an empty string."); + } + if (![self.query isCollectionGroupQuery] && [documentKey containsString:@"/"]) { + ThrowInvalidArgument("Invalid query. When querying a collection by document ID you must " + "provide a plain document ID, but '%s' contains a '/' character.", + documentKey); + } + ResourcePath path = + self.query.path.Append(ResourcePath::FromString([documentKey UTF8String])); + if (!DocumentKey::IsDocumentKey(path)) { + ThrowInvalidArgument("Invalid query. When querying a collection group by document ID, the " + "value provided must result in a valid document path, but '%s' is not " + "because it has an odd number of segments.", + path.CanonicalString()); } - ResourcePath path = self.query.path.Append([documentKey UTF8String]); fieldValue = [FSTReferenceValue referenceValue:[FSTDocumentKey keyWithDocumentKey:DocumentKey{path}] databaseID:self.firestore.databaseID]; @@ -489,9 +490,9 @@ - (FIRQuery *)queryWithFilterOperator:(FSTRelationFilterOperator)filterOperator fieldValue = [FSTReferenceValue referenceValue:[FSTDocumentKey keyWithDocumentKey:ref.key] databaseID:self.firestore.databaseID]; } else { - FSTThrowInvalidArgument(@"Invalid query. When querying by document ID you must provide a " - "valid string or DocumentReference, but it was of type: %@", - NSStringFromClass([value class])); + ThrowInvalidArgument("Invalid query. When querying by document ID you must provide a valid " + "string or DocumentReference, but it was of type: %s", + NSStringFromClass([value class])); } } else { fieldValue = [self.firestore.dataConverter parsedQueryValue:value]; @@ -513,22 +514,20 @@ - (void)validateNewRelationFilter:(FSTRelationFilter *)filter { if ([filter isInequality]) { const FieldPath *existingField = [self.query inequalityFilterField]; if (existingField && *existingField != filter.field) { - FSTThrowInvalidUsage( - @"InvalidQueryException", - @"Invalid Query. All where filters with an inequality " - "(lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on the same " - "field. But you have inequality filters on '%s' and '%s'", - existingField->CanonicalString().c_str(), filter.field.CanonicalString().c_str()); + ThrowInvalidArgument( + "Invalid Query. All where filters with an inequality " + "(lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on the same " + "field. But you have inequality filters on '%s' and '%s'", + existingField->CanonicalString(), filter.field.CanonicalString()); } const FieldPath *firstOrderByField = [self.query firstSortOrderField]; if (firstOrderByField) { [self validateOrderByField:*firstOrderByField matchesInequalityField:filter.field]; } - } else if (filter.filterOperator == FSTRelationFilterOperatorArrayContains) { + } else if (filter.filterOperator == Filter::Operator::ArrayContains) { if ([self.query hasArrayContainsFilter]) { - FSTThrowInvalidUsage(@"InvalidQueryException", - @"Invalid Query. Queries only support a single arrayContains filter."); + ThrowInvalidArgument("Invalid Query. Queries only support a single arrayContains filter."); } } } @@ -546,14 +545,12 @@ - (void)validateNewOrderByPath:(const FieldPath &)fieldPath { - (void)validateOrderByField:(const FieldPath &)orderByField matchesInequalityField:(const FieldPath &)inequalityField { if (orderByField != inequalityField) { - FSTThrowInvalidUsage( - @"InvalidQueryException", - @"Invalid query. You have a where filter with an " - "inequality (lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) on field '%s' " - "and so you must also use '%s' as your first queryOrderedBy field, but your first " - "queryOrderedBy is currently on field '%s' instead.", - inequalityField.CanonicalString().c_str(), inequalityField.CanonicalString().c_str(), - orderByField.CanonicalString().c_str()); + ThrowInvalidArgument("Invalid query. You have a where filter with an inequality " + "(lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) on field " + "'%s' and so you must also use '%s' as your first queryOrderedBy field, " + "but your first queryOrderedBy is currently on field '%s' instead.", + inequalityField.CanonicalString(), inequalityField.CanonicalString(), + orderByField.CanonicalString()); } } @@ -569,9 +566,8 @@ - (void)validateOrderByField:(const FieldPath &)orderByField */ - (FSTBound *)boundFromSnapshot:(FIRDocumentSnapshot *)snapshot isBefore:(BOOL)isBefore { if (![snapshot exists]) { - FSTThrowInvalidUsage(@"InvalidQueryException", - @"Invalid query. You are trying to start or end a query using a document " - @"that doesn't exist."); + ThrowInvalidArgument("Invalid query. You are trying to start or end a query using a document " + "that doesn't exist."); } FSTDocument *document = snapshot.internalDocument; NSMutableArray *components = [NSMutableArray array]; @@ -589,21 +585,19 @@ - (FSTBound *)boundFromSnapshot:(FIRDocumentSnapshot *)snapshot isBefore:(BOOL)i } else { FSTFieldValue *value = [document fieldForPath:sortOrder.field]; - if ([value isKindOfClass:[FSTServerTimestampValue class]]) { - FSTThrowInvalidUsage(@"InvalidQueryException", - @"Invalid query. You are trying to start or end a query using a " - "document for which the field '%s' is an uncommitted server " - "timestamp. (Since the value of this field is unknown, you cannot " - "start/end a query with it.)", - sortOrder.field.CanonicalString().c_str()); + if (value.type == FieldValue::Type::ServerTimestamp) { + ThrowInvalidArgument( + "Invalid query. You are trying to start or end a query using a document for which the " + "field '%s' is an uncommitted server timestamp. (Since the value of this field is " + "unknown, you cannot start/end a query with it.)", + sortOrder.field.CanonicalString()); } else if (value != nil) { [components addObject:value]; } else { - FSTThrowInvalidUsage(@"InvalidQueryException", - @"Invalid query. You are trying to start or end a query using a " - "document for which the field '%s' (used as the order by) " - "does not exist.", - sortOrder.field.CanonicalString().c_str()); + ThrowInvalidArgument( + "Invalid query. You are trying to start or end a query using a document for which the " + "field '%s' (used as the order by) does not exist.", + sortOrder.field.CanonicalString()); } } } @@ -615,9 +609,8 @@ - (FSTBound *)boundFromFieldValues:(NSArray *)fieldValues isBefore:(BOOL)isB // Use explicit sort order because it has to match the query the user made NSArray *explicitSortOrders = self.query.explicitSortOrders; if (fieldValues.count > explicitSortOrders.count) { - FSTThrowInvalidUsage(@"InvalidQueryException", - @"Invalid query. You are trying to start or end a query using more values " - @"than were specified in the order by."); + ThrowInvalidArgument("Invalid query. You are trying to start or end a query using more values " + "than were specified in the order by."); } NSMutableArray *components = [NSMutableArray array]; @@ -625,15 +618,22 @@ - (FSTBound *)boundFromFieldValues:(NSArray *)fieldValues isBefore:(BOOL)isB FSTSortOrder *sortOrder = explicitSortOrders[idx]; if (sortOrder.field == FieldPath::KeyFieldPath()) { if (![rawValue isKindOfClass:[NSString class]]) { - FSTThrowInvalidUsage(@"InvalidQueryException", - @"Invalid query. Expected a string for the document ID."); + ThrowInvalidArgument("Invalid query. Expected a string for the document ID."); } NSString *documentID = (NSString *)rawValue; - if ([documentID containsString:@"/"]) { - FSTThrowInvalidUsage(@"InvalidQueryException", - @"Invalid query. Document ID '%@' contains a slash.", documentID); + if (![self.query isCollectionGroupQuery] && [documentID containsString:@"/"]) { + ThrowInvalidArgument("Invalid query. When querying a collection and ordering by document " + "ID, you must pass a plain document ID, but '%s' contains a slash.", + documentID); + } + ResourcePath path = self.query.path.Append(ResourcePath::FromString([documentID UTF8String])); + if (!DocumentKey::IsDocumentKey(path)) { + ThrowInvalidArgument("Invalid query. When querying a collection group and ordering by " + "document ID, you must pass a value that results in a valid document " + "path, but '%s' is not because it contains an odd number of segments.", + path.CanonicalString()); } - const DocumentKey key{self.query.path.Append([documentID UTF8String])}; + DocumentKey key{path}; [components addObject:[FSTReferenceValue referenceValue:[FSTDocumentKey keyWithDocumentKey:key] databaseID:self.firestore.databaseID]]; @@ -646,13 +646,6 @@ - (FSTBound *)boundFromFieldValues:(NSArray *)fieldValues isBefore:(BOOL)isB return [FSTBound boundWithPosition:components isBefore:isBefore]; } -/** Converts the public API options object to the internal options object. */ -- (FSTListenOptions *)internalOptionsForIncludeMetadataChanges:(BOOL)includeMetadataChanges { - return [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:includeMetadataChanges - includeDocumentMetadataChanges:includeMetadataChanges - waitForSyncWhenOnline:NO]; -} - @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/FIRQuerySnapshot+Internal.h b/Firestore/Source/API/FIRQuerySnapshot+Internal.h index f6812f54d34..96589d1a3dd 100644 --- a/Firestore/Source/API/FIRQuerySnapshot+Internal.h +++ b/Firestore/Source/API/FIRQuerySnapshot+Internal.h @@ -16,22 +16,31 @@ #import "FIRQuerySnapshot.h" +#include "Firestore/core/src/firebase/firestore/api/firestore.h" +#include "Firestore/core/src/firebase/firestore/api/query_snapshot.h" +#include "Firestore/core/src/firebase/firestore/api/snapshot_metadata.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" @class FIRFirestore; @class FIRSnapshotMetadata; -@class FSTDocumentSet; @class FSTQuery; +using firebase::firestore::api::Firestore; +using firebase::firestore::api::QuerySnapshot; +using firebase::firestore::api::SnapshotMetadata; +using firebase::firestore::core::ViewSnapshot; + NS_ASSUME_NONNULL_BEGIN /** Internal FIRQuerySnapshot API we don't want exposed in our public header files. */ -@interface FIRQuerySnapshot (Internal) +@interface FIRQuerySnapshot (/* Init */) + +- (instancetype)initWithSnapshot:(QuerySnapshot &&)snapshot NS_DESIGNATED_INITIALIZER; -+ (instancetype)snapshotWithFirestore:(FIRFirestore *)firestore - originalQuery:(FSTQuery *)query - snapshot:(firebase::firestore::core::ViewSnapshot)snapshot - metadata:(FIRSnapshotMetadata *)metadata; +- (instancetype)initWithFirestore:(Firestore *)firestore + originalQuery:(FSTQuery *)query + snapshot:(ViewSnapshot &&)snapshot + metadata:(SnapshotMetadata)metadata; @end diff --git a/Firestore/Source/API/FIRQuerySnapshot.mm b/Firestore/Source/API/FIRQuerySnapshot.mm index c513f29c212..7ff14860f3c 100644 --- a/Firestore/Source/API/FIRQuerySnapshot.mm +++ b/Firestore/Source/API/FIRQuerySnapshot.mm @@ -18,135 +18,99 @@ #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" -#import "FIRFirestore.h" #import "FIRSnapshotMetadata.h" #import "Firestore/Source/API/FIRDocumentChange+Internal.h" #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRQuery+Internal.h" +#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" +#include "Firestore/core/src/firebase/firestore/util/delayed_constructor.h" +using firebase::firestore::api::Firestore; +using firebase::firestore::api::QuerySnapshot; +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::core::ViewSnapshot; +using firebase::firestore::util::DelayedConstructor; NS_ASSUME_NONNULL_BEGIN -@interface FIRQuerySnapshot () - -- (instancetype)initWithFirestore:(FIRFirestore *)firestore - originalQuery:(FSTQuery *)query - snapshot:(ViewSnapshot &&)snapshot - metadata:(FIRSnapshotMetadata *)metadata; - -@property(nonatomic, strong, readonly) FIRFirestore *firestore; -@property(nonatomic, strong, readonly) FSTQuery *originalQuery; -- (const ViewSnapshot &)snapshot; - -@end - -@implementation FIRQuerySnapshot (Internal) - -+ (instancetype)snapshotWithFirestore:(FIRFirestore *)firestore - originalQuery:(FSTQuery *)query - snapshot:(ViewSnapshot)snapshot - metadata:(FIRSnapshotMetadata *)metadata { - return [[FIRQuerySnapshot alloc] initWithFirestore:firestore - originalQuery:query - snapshot:std::move(snapshot) - metadata:metadata]; -} +@implementation FIRQuerySnapshot { + DelayedConstructor _snapshot; -@end + FIRSnapshotMetadata *_cached_metadata; -@implementation FIRQuerySnapshot { // Cached value of the documents property. NSArray *_documents; // Cached value of the documentChanges property. NSArray *_documentChanges; BOOL _documentChangesIncludeMetadataChanges; - - ViewSnapshot _snapshot; } -- (instancetype)initWithFirestore:(FIRFirestore *)firestore - originalQuery:(FSTQuery *)query - snapshot:(ViewSnapshot &&)snapshot - metadata:(FIRSnapshotMetadata *)metadata { +- (instancetype)initWithSnapshot:(QuerySnapshot &&)snapshot { if (self = [super init]) { - _firestore = firestore; - _originalQuery = query; - _snapshot = std::move(snapshot); - _metadata = metadata; - _documentChangesIncludeMetadataChanges = NO; + _snapshot.Init(std::move(snapshot)); } return self; } -- (const ViewSnapshot &)snapshot { - return _snapshot; +- (instancetype)initWithFirestore:(Firestore *)firestore + originalQuery:(FSTQuery *)query + snapshot:(ViewSnapshot &&)snapshot + metadata:(SnapshotMetadata)metadata { + QuerySnapshot wrapped(firestore, query, std::move(snapshot), std::move(metadata)); + return [self initWithSnapshot:std::move(wrapped)]; } // NSObject Methods - (BOOL)isEqual:(nullable id)other { - if (other == self) return YES; - if (![[other class] isEqual:[self class]]) return NO; + if (![other isKindOfClass:[FIRQuerySnapshot class]]) return NO; - return [self isEqualToSnapshot:other]; + FIRQuerySnapshot *otherSnapshot = other; + return *_snapshot == *(otherSnapshot->_snapshot); } -- (BOOL)isEqualToSnapshot:(nullable FIRQuerySnapshot *)snapshot { - if (self == snapshot) return YES; - if (snapshot == nil) return NO; +- (NSUInteger)hash { + return _snapshot->Hash(); +} - return [self.firestore isEqual:snapshot.firestore] && - [self.originalQuery isEqual:snapshot.originalQuery] && _snapshot == snapshot.snapshot && - [self.metadata isEqual:snapshot.metadata]; +- (FIRQuery *)query { + FIRFirestore *firestore = [FIRFirestore recoverFromFirestore:_snapshot->firestore()]; + return [FIRQuery referenceWithQuery:_snapshot->internal_query() firestore:firestore]; } -- (NSUInteger)hash { - NSUInteger hash = [self.firestore hash]; - hash = hash * 31u + [self.originalQuery hash]; - hash = hash * 31u + _snapshot.Hash(); - hash = hash * 31u + [self.metadata hash]; - return hash; +- (FIRSnapshotMetadata *)metadata { + if (!_cached_metadata) { + _cached_metadata = [[FIRSnapshotMetadata alloc] initWithMetadata:_snapshot->metadata()]; + } + return _cached_metadata; } @dynamic empty; -- (FIRQuery *)query { - return [FIRQuery referenceWithQuery:self.originalQuery firestore:self.firestore]; -} - - (BOOL)isEmpty { - return _snapshot.documents().isEmpty; + return _snapshot->empty(); } // This property is exposed as an NSInteger instead of an NSUInteger since (as of Xcode 8.1) // Swift bridges NSUInteger as UInt, and we want to avoid forcing Swift users to cast their ints // where we can. See cr/146959032 for additional context. - (NSInteger)count { - return _snapshot.documents().count; + return static_cast(_snapshot->size()); } - (NSArray *)documents { if (!_documents) { - FSTDocumentSet *documentSet = _snapshot.documents(); - FIRFirestore *firestore = self.firestore; - BOOL fromCache = self.metadata.fromCache; - NSMutableArray *result = [NSMutableArray array]; - for (FSTDocument *document in documentSet.documentEnumerator) { - [result addObject:[FIRQueryDocumentSnapshot - snapshotWithFirestore:firestore - documentKey:document.key - document:document - fromCache:fromCache - hasPendingWrites:_snapshot.mutated_keys().contains(document.key)]]; - } + _snapshot->ForEachDocument([&result](DocumentSnapshot snapshot) { + [result addObject:[[FIRQueryDocumentSnapshot alloc] initWithSnapshot:std::move(snapshot)]]; + }); _documents = result; } @@ -159,16 +123,15 @@ - (NSInteger)count { - (NSArray *)documentChangesWithIncludeMetadataChanges: (BOOL)includeMetadataChanges { - if (includeMetadataChanges && _snapshot.excludes_metadata_changes()) { - FSTThrowInvalidArgument( - @"To include metadata changes with your document changes, you must call " - @"addSnapshotListener(includeMetadataChanges: true)."); - } - if (!_documentChanges || _documentChangesIncludeMetadataChanges != includeMetadataChanges) { - _documentChanges = [FIRDocumentChange documentChangesForSnapshot:_snapshot - includeMetadataChanges:includeMetadataChanges - firestore:self.firestore]; + NSMutableArray *documentChanges = [NSMutableArray array]; + _snapshot->ForEachChange( + static_cast(includeMetadataChanges), [&documentChanges](DocumentChange change) { + [documentChanges + addObject:[[FIRDocumentChange alloc] initWithDocumentChange:std::move(change)]]; + }); + + _documentChanges = documentChanges; _documentChangesIncludeMetadataChanges = includeMetadataChanges; } return _documentChanges; diff --git a/Firestore/Source/API/FIRSnapshotMetadata+Internal.h b/Firestore/Source/API/FIRSnapshotMetadata+Internal.h index d3265cd0959..6c5c6997bff 100644 --- a/Firestore/Source/API/FIRSnapshotMetadata+Internal.h +++ b/Firestore/Source/API/FIRSnapshotMetadata+Internal.h @@ -18,11 +18,17 @@ #import +#include "Firestore/core/src/firebase/firestore/api/snapshot_metadata.h" + +using firebase::firestore::api::SnapshotMetadata; + NS_ASSUME_NONNULL_BEGIN -@interface FIRSnapshotMetadata (Internal) +@interface FIRSnapshotMetadata (/* Init */) + +- (instancetype)initWithMetadata:(SnapshotMetadata)metadata NS_DESIGNATED_INITIALIZER; -+ (instancetype)snapshotMetadataWithPendingWrites:(BOOL)pendingWrites fromCache:(BOOL)fromCache; +- (instancetype)initWithPendingWrites:(bool)pendingWrites fromCache:(bool)fromCache; @end diff --git a/Firestore/Source/API/FIRSnapshotMetadata.mm b/Firestore/Source/API/FIRSnapshotMetadata.mm index 27747cee260..87fa45efa23 100644 --- a/Firestore/Source/API/FIRSnapshotMetadata.mm +++ b/Firestore/Source/API/FIRSnapshotMetadata.mm @@ -16,53 +16,49 @@ #import "FIRSnapshotMetadata.h" -#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRSnapshotMetadata () +#include -- (instancetype)initWithPendingWrites:(BOOL)pendingWrites fromCache:(BOOL)fromCache; +#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" -@end +#include "Firestore/core/src/firebase/firestore/api/snapshot_metadata.h" -@implementation FIRSnapshotMetadata (Internal) +NS_ASSUME_NONNULL_BEGIN -+ (instancetype)snapshotMetadataWithPendingWrites:(BOOL)pendingWrites fromCache:(BOOL)fromCache { - return [[FIRSnapshotMetadata alloc] initWithPendingWrites:pendingWrites fromCache:fromCache]; +@implementation FIRSnapshotMetadata { + SnapshotMetadata _metadata; } -@end - -@implementation FIRSnapshotMetadata - -- (instancetype)initWithPendingWrites:(BOOL)pendingWrites fromCache:(BOOL)fromCache { +- (instancetype)initWithMetadata:(SnapshotMetadata)metadata { if (self = [super init]) { - _pendingWrites = pendingWrites; - _fromCache = fromCache; + _metadata = std::move(metadata); } return self; } +- (instancetype)initWithPendingWrites:(bool)pendingWrites fromCache:(bool)fromCache { + SnapshotMetadata wrapped(pendingWrites, fromCache); + return [self initWithMetadata:std::move(wrapped)]; +} + // NSObject Methods - (BOOL)isEqual:(nullable id)other { if (other == self) return YES; - if (![[other class] isEqual:[self class]]) return NO; + if (![other isKindOfClass:[FIRSnapshotMetadata class]]) return NO; - return [self isEqualToMetadata:other]; + FIRSnapshotMetadata *otherMetadata = other; + return _metadata == otherMetadata->_metadata; } -- (BOOL)isEqualToMetadata:(nullable FIRSnapshotMetadata *)metadata { - if (self == metadata) return YES; - if (metadata == nil) return NO; +- (NSUInteger)hash { + return _metadata.Hash(); +} - return self.pendingWrites == metadata.pendingWrites && self.fromCache == metadata.fromCache; +- (BOOL)hasPendingWrites { + return _metadata.pending_writes(); } -- (NSUInteger)hash { - NSUInteger hash = self.pendingWrites ? 1 : 0; - hash = hash * 31u + (self.fromCache ? 1 : 0); - return hash; +- (BOOL)isFromCache { + return _metadata.from_cache(); } @end diff --git a/Firestore/Source/API/FIRTransaction.mm b/Firestore/Source/API/FIRTransaction.mm index 044c652dc3e..d00eef48d5e 100644 --- a/Firestore/Source/API/FIRTransaction.mm +++ b/Firestore/Source/API/FIRTransaction.mm @@ -26,13 +26,14 @@ #import "Firestore/Source/API/FIRTransaction+Internal.h" #import "Firestore/Source/API/FSTUserDataConverter.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/core/transaction.h" #include "Firestore/core/src/firebase/firestore/util/error_apple.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/status.h" +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::core::ParsedSetData; using firebase::firestore::core::ParsedUpdateData; using firebase::firestore::core::Transaction; @@ -127,19 +128,20 @@ - (void)getDocument:(FIRDocumentReference *)document HARD_ASSERT(documents.size() == 1, "Mismatch in docs returned from document lookup."); FSTMaybeDocument *internalDoc = documents.front(); if ([internalDoc isKindOfClass:[FSTDeletedDocument class]]) { - FIRDocumentSnapshot *doc = [FIRDocumentSnapshot snapshotWithFirestore:self.firestore - documentKey:document.key - document:nil - fromCache:NO - hasPendingWrites:NO]; + FIRDocumentSnapshot *doc = + [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore.wrapped + documentKey:document.key + document:nil + fromCache:false + hasPendingWrites:false]; completion(doc, nil); } else if ([internalDoc isKindOfClass:[FSTDocument class]]) { FIRDocumentSnapshot *doc = - [FIRDocumentSnapshot snapshotWithFirestore:self.firestore - documentKey:internalDoc.key - document:(FSTDocument *)internalDoc - fromCache:NO - hasPendingWrites:NO]; + [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore.wrapped + documentKey:internalDoc.key + document:(FSTDocument *)internalDoc + fromCache:false + hasPendingWrites:false]; completion(doc, nil); } else { HARD_FAIL("BatchGetDocumentsRequest returned unexpected document type: %s", @@ -170,7 +172,7 @@ - (FIRDocumentSnapshot *_Nullable)getDocument:(FIRDocumentReference *)document - (void)validateReference:(FIRDocumentReference *)reference { if (reference.firestore != self.firestore) { - FSTThrowInvalidArgument(@"Provided document reference is from a different Firestore instance."); + ThrowInvalidArgument("Provided document reference is from a different Firestore instance."); } } diff --git a/Firestore/Source/API/FIRWriteBatch.mm b/Firestore/Source/API/FIRWriteBatch.mm index 484b8b0f4e8..d95576f1527 100644 --- a/Firestore/Source/API/FIRWriteBatch.mm +++ b/Firestore/Source/API/FIRWriteBatch.mm @@ -26,10 +26,12 @@ #import "Firestore/Source/API/FSTUserDataConverter.h" #import "Firestore/Source/Core/FSTFirestoreClient.h" #import "Firestore/Source/Model/FSTMutation.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/model/precondition.h" +using firebase::firestore::api::ThrowIllegalState; +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::core::ParsedSetData; using firebase::firestore::core::ParsedUpdateData; using firebase::firestore::model::Precondition; @@ -131,19 +133,18 @@ - (void)commit { - (void)commitWithCompletion:(nullable void (^)(NSError *_Nullable error))completion { [self verifyNotCommitted]; self.committed = TRUE; - [self.firestore.client writeMutations:std::move(_mutations) completion:completion]; + [self.firestore.wrapped->client() writeMutations:std::move(_mutations) completion:completion]; } - (void)verifyNotCommitted { if (self.committed) { - FSTThrowInvalidUsage(@"FIRIllegalStateException", - @"A write batch can no longer be used after commit has been called."); + ThrowIllegalState("A write batch can no longer be used after commit has been called."); } } - (void)validateReference:(FIRDocumentReference *)reference { if (reference.firestore != self.firestore) { - FSTThrowInvalidArgument(@"Provided document reference is from a different Firestore instance."); + ThrowInvalidArgument("Provided document reference is from a different Firestore instance."); } } diff --git a/Firestore/Source/API/FSTFirestoreComponent.mm b/Firestore/Source/API/FSTFirestoreComponent.mm index 4791c121442..20df5ddd13f 100644 --- a/Firestore/Source/API/FSTFirestoreComponent.mm +++ b/Firestore/Source/API/FSTFirestoreComponent.mm @@ -29,8 +29,9 @@ #include #import "Firestore/Source/API/FIRFirestore+Internal.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" #include "Firestore/core/include/firebase/firestore/firestore_version.h" +#include "Firestore/core/src/firebase/firestore/api/firestore.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" #include "Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h" #include "Firestore/core/src/firebase/firestore/util/async_queue.h" @@ -39,6 +40,8 @@ #include "absl/memory/memory.h" namespace util = firebase::firestore::util; +using firebase::firestore::api::Firestore; +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::auth::CredentialsProvider; using firebase::firestore::auth::FirebaseCredentialsProvider; using util::AsyncQueue; @@ -71,7 +74,7 @@ - (instancetype)initWithApp:(FIRApp *)app { - (FIRFirestore *)firestoreForDatabase:(NSString *)database { if (!database) { - FSTThrowInvalidArgument(@"database identifier may not be nil."); + ThrowInvalidArgument("Database identifier may not be nil."); } NSString *key = [NSString stringWithFormat:@"%@|%@", self.app.name, database]; @@ -90,15 +93,14 @@ - (FIRFirestore *)firestoreForDatabase:(NSString *)database { auto workerQueue = absl::make_unique(std::move(executor)); id auth = FIR_COMPONENT(FIRAuthInterop, self.app.container); - std::unique_ptr credentials_provider = - absl::make_unique(self.app, auth); + auto credentialsProvider = absl::make_unique(self.app, auth); - NSString *persistenceKey = self.app.name; - NSString *projectID = self.app.options.projectID; - firestore = [[FIRFirestore alloc] initWithProjectID:util::MakeString(projectID) + std::string projectID = util::MakeString(self.app.options.projectID); + std::string persistenceKey = util::MakeString(self.app.name); + firestore = [[FIRFirestore alloc] initWithProjectID:std::move(projectID) database:util::MakeString(database) - persistenceKey:persistenceKey - credentialsProvider:std::move(credentials_provider) + persistenceKey:std::move(persistenceKey) + credentialsProvider:std::move(credentialsProvider) workerQueue:std::move(workerQueue) firebaseApp:self.app]; _instances[key] = firestore; diff --git a/Firestore/Source/API/FSTUserDataConverter.mm b/Firestore/Source/API/FSTUserDataConverter.mm index 0f0abd80114..22ba5396b30 100644 --- a/Firestore/Source/API/FSTUserDataConverter.mm +++ b/Firestore/Source/API/FSTUserDataConverter.mm @@ -31,8 +31,8 @@ #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/Model/FSTFieldValue.h" #import "Firestore/Source/Model/FSTMutation.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/core/user_data.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" @@ -47,6 +47,7 @@ #include "absl/strings/match.h" namespace util = firebase::firestore::util; +using firebase::firestore::api::ThrowInvalidArgument; using firebase::firestore::core::ParsedSetData; using firebase::firestore::core::ParsedUpdateData; using firebase::firestore::core::ParseAccumulator; @@ -110,7 +111,7 @@ - (ParsedSetData)parsedSetData:(id)input { // NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust // Obj-C to verify the type for us. if (![input isKindOfClass:[NSDictionary class]]) { - FSTThrowInvalidArgument(@"Data to be written must be an NSDictionary."); + ThrowInvalidArgument("Data to be written must be an NSDictionary."); } ParseAccumulator accumulator{UserDataSource::Set}; @@ -123,7 +124,7 @@ - (ParsedSetData)parsedMergeData:(id)input fieldMask:(nullable NSArray *)fie // NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust // Obj-C to verify the type for us. if (![input isKindOfClass:[NSDictionary class]]) { - FSTThrowInvalidArgument(@"Data to be written must be an NSDictionary."); + ThrowInvalidArgument("Data to be written must be an NSDictionary."); } ParseAccumulator accumulator{UserDataSource::MergeSet}; @@ -141,15 +142,14 @@ - (ParsedSetData)parsedMergeData:(id)input fieldMask:(nullable NSArray *)fie } else if ([fieldPath isKindOfClass:[FIRFieldPath class]]) { path = ((FIRFieldPath *)fieldPath).internalValue; } else { - FSTThrowInvalidArgument( - @"All elements in mergeFields: must be NSStrings or FIRFieldPaths."); + ThrowInvalidArgument("All elements in mergeFields: must be NSStrings or FIRFieldPaths."); } // Verify that all elements specified in the field mask are part of the parsed context. if (!accumulator.Contains(path)) { - FSTThrowInvalidArgument( - @"Field '%s' is specified in your field mask but missing from your input data.", - path.CanonicalString().c_str()); + ThrowInvalidArgument( + "Field '%s' is specified in your field mask but missing from your input data.", + path.CanonicalString()); } validatedFieldPaths.insert(path); @@ -166,7 +166,7 @@ - (ParsedUpdateData)parsedUpdateData:(id)input { // NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust // Obj-C to verify the type for us. if (![input isKindOfClass:[NSDictionary class]]) { - FSTThrowInvalidArgument(@"Data to be written must be an NSDictionary."); + ThrowInvalidArgument("Data to be written must be an NSDictionary."); } NSDictionary *dict = input; @@ -183,8 +183,7 @@ - (ParsedUpdateData)parsedUpdateData:(id)input { } else if ([key isKindOfClass:[FIRFieldPath class]]) { path = ((FIRFieldPath *)key).internalValue; } else { - FSTThrowInvalidArgument( - @"Dictionary keys in updateData: must be NSStrings or FIRFieldPaths."); + ThrowInvalidArgument("Dictionary keys in updateData: must be NSStrings or FIRFieldPaths."); } value = self.preConverter(value); @@ -247,7 +246,7 @@ - (nullable FSTFieldValue *)parseData:(id)input context:(ParseContext &&)context if ([input isKindOfClass:[NSArray class]]) { // TODO(b/34871131): Include the path containing the array in the error message. if (context.array_element()) { - FSTThrowInvalidArgument(@"Nested arrays are not supported"); + ThrowInvalidArgument("Nested arrays are not supported"); } return [self parseArray:(NSArray *)input context:std::move(context)]; } else { @@ -298,11 +297,11 @@ - (FSTFieldValue *)parseArray:(NSArray *)array context:(ParseContext &&)context - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContext &&)context { // Sentinels are only supported with writes, and not within arrays. if (!context.write()) { - FSTThrowInvalidArgument(@"%@ can only be used with updateData() and setData()%s", - fieldValue.methodName, context.FieldDescription().c_str()); + ThrowInvalidArgument("%s can only be used with updateData() and setData()%s", + fieldValue.methodName, context.FieldDescription()); } if (!context.path()) { - FSTThrowInvalidArgument(@"%@ is not currently supported inside arrays", fieldValue.methodName); + ThrowInvalidArgument("%s is not currently supported inside arrays", fieldValue.methodName); } if ([fieldValue isKindOfClass:[FSTDeleteFieldValue class]]) { @@ -314,15 +313,14 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex } else if (context.data_source() == UserDataSource::Update) { HARD_ASSERT(context.path()->size() > 0, "FieldValue.delete() at the top level should have already been handled."); - FSTThrowInvalidArgument(@"FieldValue.delete() can only appear at the top level of your " - "update data%s", - context.FieldDescription().c_str()); + ThrowInvalidArgument("FieldValue.delete() can only appear at the top level of your " + "update data%s", + context.FieldDescription()); } else { // We shouldn't encounter delete sentinels for queries or non-merge setData calls. - FSTThrowInvalidArgument( - @"FieldValue.delete() can only be used with updateData() and setData() with " - @"merge:true%s", - context.FieldDescription().c_str()); + ThrowInvalidArgument( + "FieldValue.delete() can only be used with updateData() and setData() with merge:true%s", + context.FieldDescription()); } } else if ([fieldValue isKindOfClass:[FSTServerTimestampFieldValue class]]) { @@ -400,9 +398,8 @@ - (nullable FSTFieldValue *)parseScalarValue:(nullable id)input context:(ParseCo unsigned long long extended = [input unsignedLongLongValue]; if (extended > LLONG_MAX) { - FSTThrowInvalidArgument(@"NSNumber (%llu) is too large%s", - [input unsignedLongLongValue], - context.FieldDescription().c_str()); + ThrowInvalidArgument("NSNumber (%s) is too large%s", [input unsignedLongLongValue], + context.FieldDescription()); } else { return [FSTIntegerValue integerValue:(int64_t)extended]; @@ -429,7 +426,7 @@ - (nullable FSTFieldValue *)parseScalarValue:(nullable id)input context:(ParseCo // legitimate usage of signed chars is impossible, but this should be rare. // // Additionally, for consistency, map unsigned chars to bools in the same way. - return [FSTBooleanValue booleanValue:[input boolValue]]; + return FieldValue::FromBoolean([input boolValue]).Wrap(); default: // All documented codes should be handled above, so this shouldn't happen. @@ -437,7 +434,7 @@ - (nullable FSTFieldValue *)parseScalarValue:(nullable id)input context:(ParseCo } } else if ([input isKindOfClass:[NSString class]]) { - return [FSTStringValue stringValue:input]; + return FieldValue::FromString(util::MakeString(input)).Wrap(); } else if ([input isKindOfClass:[NSDate class]]) { return [FSTTimestampValue timestampValue:[FIRTimestamp timestampWithDate:input]]; @@ -459,18 +456,17 @@ - (nullable FSTFieldValue *)parseScalarValue:(nullable id)input context:(ParseCo FSTDocumentKeyReference *reference = input; if (*reference.databaseID != *self.databaseID) { const DatabaseId *other = reference.databaseID; - FSTThrowInvalidArgument( - @"Document Reference is for database %s/%s but should be for database %s/%s%s", - other->project_id().c_str(), other->database_id().c_str(), - self.databaseID->project_id().c_str(), self.databaseID->database_id().c_str(), - context.FieldDescription().c_str()); + ThrowInvalidArgument( + "Document Reference is for database %s/%s but should be for database %s/%s%s", + other->project_id(), other->database_id(), self.databaseID->project_id(), + self.databaseID->database_id(), context.FieldDescription()); } return [FSTReferenceValue referenceValue:[FSTDocumentKey keyWithDocumentKey:reference.key] databaseID:self.databaseID]; } else { - FSTThrowInvalidArgument(@"Unsupported type: %@%s", NSStringFromClass([input class]), - context.FieldDescription().c_str()); + ThrowInvalidArgument("Unsupported type: %s%s", NSStringFromClass([input class]), + context.FieldDescription()); } } diff --git a/Firestore/Source/Core/FSTEventManager.h b/Firestore/Source/Core/FSTEventManager.h index 30be8c08bed..c3d9639c072 100644 --- a/Firestore/Source/Core/FSTEventManager.h +++ b/Firestore/Source/Core/FSTEventManager.h @@ -16,6 +16,9 @@ #import +#include + +#include "Firestore/core/src/firebase/firestore/core/query_listener.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" #include "Firestore/core/src/firebase/firestore/model/types.h" #include "Firestore/core/src/firebase/firestore/util/status.h" @@ -25,49 +28,7 @@ NS_ASSUME_NONNULL_BEGIN -#pragma mark - FSTListenOptions - -@interface FSTListenOptions : NSObject - -+ (instancetype)defaultOptions; - -- (instancetype)initWithIncludeQueryMetadataChanges:(BOOL)includeQueryMetadataChanges - includeDocumentMetadataChanges:(BOOL)includeDocumentMetadataChanges - waitForSyncWhenOnline:(BOOL)waitForSyncWhenOnline - NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -@property(nonatomic, assign, readonly) BOOL includeQueryMetadataChanges; - -@property(nonatomic, assign, readonly) BOOL includeDocumentMetadataChanges; - -@property(nonatomic, assign, readonly) BOOL waitForSyncWhenOnline; - -@end - -#pragma mark - FSTQueryListener - -/** - * FSTQueryListener takes a series of internal view snapshots and determines when to raise - * user-facing events. - */ -@interface FSTQueryListener : NSObject - -- (instancetype)initWithQuery:(FSTQuery *)query - options:(FSTListenOptions *)options - viewSnapshotHandler:(firebase::firestore::core::ViewSnapshotHandler &&)viewSnapshotHandler - NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -- (void)queryDidChangeViewSnapshot:(firebase::firestore::core::ViewSnapshot)snapshot; -- (void)queryDidError:(const firebase::firestore::util::Status &)error; -- (void)applyChangedOnlineState:(firebase::firestore::model::OnlineState)onlineState; - -@property(nonatomic, strong, readonly) FSTQuery *query; - -@end +using firebase::firestore::core::QueryListener; #pragma mark - FSTEventManager @@ -81,8 +42,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init __attribute__((unavailable("Use static constructor method."))); -- (firebase::firestore::model::TargetId)addListener:(FSTQueryListener *)listener; -- (void)removeListener:(FSTQueryListener *)listener; +- (firebase::firestore::model::TargetId)addListener:(std::shared_ptr)listener; +- (void)removeListener:(const std::shared_ptr &)listener; - (void)applyChangedOnlineState:(firebase::firestore::model::OnlineState)onlineState; diff --git a/Firestore/Source/Core/FSTEventManager.mm b/Firestore/Source/Core/FSTEventManager.mm index 384dbb2910b..93f5999f3cb 100644 --- a/Firestore/Source/Core/FSTEventManager.mm +++ b/Firestore/Source/Core/FSTEventManager.mm @@ -16,238 +16,65 @@ #import "Firestore/Source/Core/FSTEventManager.h" +#include #include #include #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Core/FSTSyncEngine.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/util/error_apple.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "Firestore/core/src/firebase/firestore/util/objc_compatibility.h" #include "Firestore/core/src/firebase/firestore/util/status.h" +#include "absl/algorithm/container.h" #include "absl/types/optional.h" +NS_ASSUME_NONNULL_BEGIN + +namespace objc = firebase::firestore::util::objc; using firebase::firestore::core::DocumentViewChange; using firebase::firestore::core::ViewSnapshot; -using firebase::firestore::core::ViewSnapshotHandler; using firebase::firestore::model::OnlineState; using firebase::firestore::model::TargetId; -using firebase::firestore::util::Status; using firebase::firestore::util::MakeStatus; - -NS_ASSUME_NONNULL_BEGIN - -#pragma mark - FSTListenOptions - -@implementation FSTListenOptions - -+ (instancetype)defaultOptions { - static FSTListenOptions *defaultOptions; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultOptions = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO - includeDocumentMetadataChanges:NO - waitForSyncWhenOnline:NO]; - }); - return defaultOptions; -} - -- (instancetype)initWithIncludeQueryMetadataChanges:(BOOL)includeQueryMetadataChanges - includeDocumentMetadataChanges:(BOOL)includeDocumentMetadataChanges - waitForSyncWhenOnline:(BOOL)waitForSyncWhenOnline { - if (self = [super init]) { - _includeQueryMetadataChanges = includeQueryMetadataChanges; - _includeDocumentMetadataChanges = includeDocumentMetadataChanges; - _waitForSyncWhenOnline = waitForSyncWhenOnline; - } - return self; -} - -- (instancetype)init { - HARD_FAIL("FSTListenOptions init not supported"); - return nil; -} - -@end +using firebase::firestore::util::Status; #pragma mark - FSTQueryListenersInfo +namespace { + /** * Holds the listeners and the last received ViewSnapshot for a query being tracked by * EventManager. */ -@interface FSTQueryListenersInfo : NSObject -@property(nonatomic, assign, readwrite) TargetId targetID; -@property(nonatomic, strong, readonly) NSMutableArray *listeners; - -- (const absl::optional &)viewSnapshot; -- (void)setViewSnapshot:(const absl::optional &)snapshot; - -@end - -@implementation FSTQueryListenersInfo { - absl::optional _viewSnapshot; -} - -- (const absl::optional &)viewSnapshot { - return _viewSnapshot; -} -- (void)setViewSnapshot:(const absl::optional &)snapshot { - _viewSnapshot = snapshot; -} - -- (instancetype)init { - if (self = [super init]) { - _listeners = [NSMutableArray array]; - } - return self; -} - -@end - -#pragma mark - FSTQueryListener - -@interface FSTQueryListener () - -/** The last received view snapshot. */ -- (const absl::optional &)snapshot; - -@property(nonatomic, strong, readonly) FSTListenOptions *options; - -/** - * Initial snapshots (e.g. from cache) may not be propagated to the ViewSnapshotHandler. - * This flag is set to YES once we've actually raised an event. - */ -@property(nonatomic, assign, readwrite) BOOL raisedInitialEvent; - -/** The last online state this query listener got. */ -@property(nonatomic, assign, readwrite) OnlineState onlineState; - -@end - -@implementation FSTQueryListener { - absl::optional _snapshot; - - /** The ViewSnapshotHandler associated with this query listener. */ - ViewSnapshotHandler _viewSnapshotHandler; -} - -- (instancetype)initWithQuery:(FSTQuery *)query - options:(FSTListenOptions *)options - viewSnapshotHandler:(ViewSnapshotHandler &&)viewSnapshotHandler { - if (self = [super init]) { - _query = query; - _options = options; - _viewSnapshotHandler = std::move(viewSnapshotHandler); - _raisedInitialEvent = NO; - } - return self; -} - -- (const absl::optional &)snapshot { - return _snapshot; -} - -- (void)queryDidChangeViewSnapshot:(ViewSnapshot)snapshot { - HARD_ASSERT(!snapshot.document_changes().empty() || snapshot.sync_state_changed(), - "We got a new snapshot with no changes?"); - - if (!self.options.includeDocumentMetadataChanges) { - // Remove the metadata-only changes. - std::vector changes; - for (const DocumentViewChange &change : snapshot.document_changes()) { - if (change.type() != DocumentViewChange::Type::kMetadata) { - changes.push_back(change); - } - } - - snapshot = ViewSnapshot{snapshot.query(), - snapshot.documents(), - snapshot.old_documents(), - std::move(changes), - snapshot.mutated_keys(), - snapshot.from_cache(), - snapshot.sync_state_changed(), - /*excludes_metadata_changes=*/true}; - } - - if (!self.raisedInitialEvent) { - if ([self shouldRaiseInitialEventForSnapshot:snapshot onlineState:self.onlineState]) { - [self raiseInitialEventForSnapshot:snapshot]; +struct QueryListenersInfo { + TargetId target_id; + std::vector> listeners; + + void Erase(const std::shared_ptr &listener) { + auto found = absl::c_find(listeners, listener); + if (found != listeners.end()) { + listeners.erase(found); } - } else if ([self shouldRaiseEventForSnapshot:snapshot]) { - _viewSnapshotHandler(snapshot); - } - - _snapshot = std::move(snapshot); -} - -- (void)queryDidError:(const Status &)error { - _viewSnapshotHandler(error); -} - -- (void)applyChangedOnlineState:(OnlineState)onlineState { - self.onlineState = onlineState; - if (_snapshot.has_value() && !self.raisedInitialEvent && - [self shouldRaiseInitialEventForSnapshot:_snapshot.value() onlineState:onlineState]) { - [self raiseInitialEventForSnapshot:_snapshot.value()]; } -} - -- (BOOL)shouldRaiseInitialEventForSnapshot:(const ViewSnapshot &)snapshot - onlineState:(OnlineState)onlineState { - HARD_ASSERT(!self.raisedInitialEvent, - "Determining whether to raise initial event, but already had first event."); - - // Always raise the first event when we're synced - if (!snapshot.from_cache()) { - return YES; - } - - // NOTE: We consider OnlineState.Unknown as online (it should become Offline or Online if we - // wait long enough). - BOOL maybeOnline = onlineState != OnlineState::Offline; - // Don't raise the event if we're online, aren't synced yet (checked - // above) and are waiting for a sync. - if (self.options.waitForSyncWhenOnline && maybeOnline) { - HARD_ASSERT(snapshot.from_cache(), "Waiting for sync, but snapshot is not from cache."); - return NO; - } - - // Raise data from cache if we have any documents or we are offline - return !snapshot.documents().isEmpty || onlineState == OnlineState::Offline; -} -- (BOOL)shouldRaiseEventForSnapshot:(const ViewSnapshot &)snapshot { - // We don't need to handle includeDocumentMetadataChanges here because the Metadata only changes - // have already been stripped out if needed. At this point the only changes we will see are the - // ones we should propagate. - if (!snapshot.document_changes().empty()) { - return YES; + const absl::optional &view_snapshot() const { + return snapshot_; } - BOOL hasPendingWritesChanged = _snapshot.has_value() && _snapshot.value().has_pending_writes() != - snapshot.has_pending_writes(); - if (snapshot.sync_state_changed() || hasPendingWritesChanged) { - return self.options.includeQueryMetadataChanges; + void set_view_snapshot(const absl::optional &snapshot) { + snapshot_ = snapshot; } - // Generally we should have hit one of the cases above, but it's possible to get here if there - // were only metadata docChanges and they got stripped out. - return NO; -} - -- (void)raiseInitialEventForSnapshot:(const ViewSnapshot &)snapshot { - HARD_ASSERT(!self.raisedInitialEvent, "Trying to raise initial events for second time"); - ViewSnapshot modifiedSnapshot = ViewSnapshot::FromInitialDocuments( - snapshot.query(), snapshot.documents(), snapshot.mutated_keys(), snapshot.from_cache(), - snapshot.excludes_metadata_changes()); - self.raisedInitialEvent = YES; - _viewSnapshotHandler(modifiedSnapshot); -} + private: + // Other members are public in this struct, ensure that any reads are + // copies by requiring reads to go through a const getter. + absl::optional snapshot_; +}; -@end +} // namespace #pragma mark - FSTEventManager @@ -256,13 +83,13 @@ @interface FSTEventManager () - (instancetype)initWithSyncEngine:(FSTSyncEngine *)syncEngine NS_DESIGNATED_INITIALIZER; @property(nonatomic, strong, readonly) FSTSyncEngine *syncEngine; -@property(nonatomic, strong, readonly) - NSMutableDictionary *queries; @property(nonatomic, assign) OnlineState onlineState; @end -@implementation FSTEventManager +@implementation FSTEventManager { + objc::unordered_map _queries; +} + (instancetype)eventManagerWithSyncEngine:(FSTSyncEngine *)syncEngine { return [[FSTEventManager alloc] initWithSyncEngine:syncEngine]; @@ -271,49 +98,45 @@ + (instancetype)eventManagerWithSyncEngine:(FSTSyncEngine *)syncEngine { - (instancetype)initWithSyncEngine:(FSTSyncEngine *)syncEngine { if (self = [super init]) { _syncEngine = syncEngine; - _queries = [NSMutableDictionary dictionary]; - _syncEngine.syncEngineDelegate = self; } return self; } -- (TargetId)addListener:(FSTQueryListener *)listener { - FSTQuery *query = listener.query; - BOOL firstListen = NO; +- (TargetId)addListener:(std::shared_ptr)listener { + FSTQuery *query = listener->query(); - FSTQueryListenersInfo *queryInfo = self.queries[query]; - if (!queryInfo) { - firstListen = YES; - queryInfo = [[FSTQueryListenersInfo alloc] init]; - self.queries[query] = queryInfo; - } - [queryInfo.listeners addObject:listener]; + auto inserted = _queries.emplace(query, QueryListenersInfo{}); + bool first_listen = inserted.second; + QueryListenersInfo &query_info = inserted.first->second; + + query_info.listeners.push_back(listener); - [listener applyChangedOnlineState:self.onlineState]; + listener->OnOnlineStateChanged(self.onlineState); - if (queryInfo.viewSnapshot.has_value()) { - [listener queryDidChangeViewSnapshot:queryInfo.viewSnapshot.value()]; + if (query_info.view_snapshot().has_value()) { + listener->OnViewSnapshot(query_info.view_snapshot().value()); } - if (firstListen) { - queryInfo.targetID = [self.syncEngine listenToQuery:query]; + if (first_listen) { + query_info.target_id = [self.syncEngine listenToQuery:query]; } - return queryInfo.targetID; + return query_info.target_id; } -- (void)removeListener:(FSTQueryListener *)listener { - FSTQuery *query = listener.query; - BOOL lastListen = NO; +- (void)removeListener:(const std::shared_ptr &)listener { + FSTQuery *query = listener->query(); + bool last_listen = false; - FSTQueryListenersInfo *queryInfo = self.queries[query]; - if (queryInfo) { - [queryInfo.listeners removeObject:listener]; - lastListen = (queryInfo.listeners.count == 0); + auto found_iter = _queries.find(query); + if (found_iter != _queries.end()) { + QueryListenersInfo &query_info = found_iter->second; + query_info.Erase(listener); + last_listen = query_info.listeners.empty(); } - if (lastListen) { - [self.queries removeObjectForKey:query]; + if (last_listen) { + _queries.erase(found_iter); [self.syncEngine stopListeningToQuery:query]; } } @@ -321,33 +144,38 @@ - (void)removeListener:(FSTQueryListener *)listener { - (void)handleViewSnapshots:(std::vector &&)viewSnapshots { for (ViewSnapshot &viewSnapshot : viewSnapshots) { FSTQuery *query = viewSnapshot.query(); - FSTQueryListenersInfo *queryInfo = self.queries[query]; - if (queryInfo) { - for (FSTQueryListener *listener in queryInfo.listeners) { - [listener queryDidChangeViewSnapshot:viewSnapshot]; + auto found_iter = _queries.find(query); + if (found_iter != _queries.end()) { + QueryListenersInfo &query_info = found_iter->second; + for (const auto &listener : query_info.listeners) { + listener->OnViewSnapshot(viewSnapshot); } - [queryInfo setViewSnapshot:std::move(viewSnapshot)]; + query_info.set_view_snapshot(std::move(viewSnapshot)); } } } - (void)handleError:(NSError *)error forQuery:(FSTQuery *)query { - FSTQueryListenersInfo *queryInfo = self.queries[query]; - if (queryInfo) { - for (FSTQueryListener *listener in queryInfo.listeners) { - [listener queryDidError:MakeStatus(error)]; + auto found_iter = _queries.find(query); + if (found_iter != _queries.end()) { + QueryListenersInfo &query_info = found_iter->second; + for (const auto &listener : query_info.listeners) { + listener->OnError(MakeStatus(error)); } - } - // Remove all listeners. NOTE: We don't need to call [FSTSyncEngine stopListening] after an error. - [self.queries removeObjectForKey:query]; + // Remove all listeners. NOTE: We don't need to call [FSTSyncEngine stopListening] after an + // error. + _queries.erase(found_iter); + } } - (void)applyChangedOnlineState:(OnlineState)onlineState { self.onlineState = onlineState; - for (FSTQueryListenersInfo *info in self.queries.objectEnumerator) { - for (FSTQueryListener *listener in info.listeners) { - [listener applyChangedOnlineState:onlineState]; + + for (auto &&kv : _queries) { + QueryListenersInfo &info = kv.second; + for (auto &&listener : info.listeners) { + listener->OnOnlineStateChanged(onlineState); } } } diff --git a/Firestore/Source/Core/FSTFirestoreClient.h b/Firestore/Source/Core/FSTFirestoreClient.h index a432cb2dc6f..8d4c4cf4310 100644 --- a/Firestore/Source/Core/FSTFirestoreClient.h +++ b/Firestore/Source/Core/FSTFirestoreClient.h @@ -22,12 +22,16 @@ #import "Firestore/Source/Core/FSTTypes.h" #include "Firestore/core/src/firebase/firestore/api/document_reference.h" +#include "Firestore/core/src/firebase/firestore/api/document_snapshot.h" #include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" #include "Firestore/core/src/firebase/firestore/core/database_info.h" +#include "Firestore/core/src/firebase/firestore/core/listen_options.h" +#include "Firestore/core/src/firebase/firestore/core/query_listener.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/util/async_queue.h" #include "Firestore/core/src/firebase/firestore/util/executor.h" +#include "Firestore/core/src/firebase/firestore/util/statusor_callback.h" @class FIRDocumentReference; @class FIRDocumentSnapshot; @@ -37,14 +41,17 @@ @class FSTDatabaseID; @class FSTDatabaseInfo; @class FSTDocument; -@class FSTListenOptions; @class FSTMutation; @class FSTQuery; -@class FSTQueryListener; @class FSTTransaction; NS_ASSUME_NONNULL_BEGIN +using firebase::firestore::api::DocumentSnapshot; +using firebase::firestore::core::ListenOptions; +using firebase::firestore::core::QueryListener; +using firebase::firestore::core::ViewSnapshot; + /** * FirestoreClient is a top-level class that constructs and owns all of the pieces of the client * SDK architecture. It is responsible for creating the worker queue that is shared by all of the @@ -77,21 +84,19 @@ NS_ASSUME_NONNULL_BEGIN - (void)enableNetworkWithCompletion:(nullable FSTVoidErrorBlock)completion; /** Starts listening to a query. */ -- (FSTQueryListener *)listenToQuery:(FSTQuery *)query - options:(FSTListenOptions *)options - viewSnapshotHandler: - (firebase::firestore::core::ViewSnapshotHandler &&)viewSnapshotHandler; +- (std::shared_ptr)listenToQuery:(FSTQuery *)query + options:(ListenOptions)options + listener:(ViewSnapshot::SharedListener &&)listener; /** Stops listening to a query previously listened to. */ -- (void)removeListener:(FSTQueryListener *)listener; +- (void)removeListener:(const std::shared_ptr &)listener; /** * Retrieves a document from the cache via the indicated completion. If the doc * doesn't exist, an error will be sent to the completion. */ - (void)getDocumentFromLocalCache:(const firebase::firestore::api::DocumentReference &)doc - completion:(void (^)(FIRDocumentSnapshot *_Nullable document, - NSError *_Nullable error))completion; + completion:(DocumentSnapshot::Listener &&)completion; /** * Retrieves a (possibly empty) set of documents from the cache via the diff --git a/Firestore/Source/Core/FSTFirestoreClient.mm b/Firestore/Source/Core/FSTFirestoreClient.mm index 21bedf095f3..495e021910a 100644 --- a/Firestore/Source/Core/FSTFirestoreClient.mm +++ b/Firestore/Source/Core/FSTFirestoreClient.mm @@ -25,6 +25,7 @@ #import "FIRFirestoreSettings.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRQuery+Internal.h" #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" @@ -38,28 +39,31 @@ #import "Firestore/Source/Local/FSTLocalStore.h" #import "Firestore/Source/Local/FSTMemoryPersistence.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Source/Remote/FSTSerializerBeta.h" #import "Firestore/Source/Util/FSTClasses.h" #include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" #include "Firestore/core/src/firebase/firestore/core/database_info.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/remote/datastore.h" #include "Firestore/core/src/firebase/firestore/remote/remote_store.h" #include "Firestore/core/src/firebase/firestore/util/async_queue.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/log.h" +#include "Firestore/core/src/firebase/firestore/util/status.h" +#include "Firestore/core/src/firebase/firestore/util/statusor.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" #include "absl/memory/memory.h" namespace util = firebase::firestore::util; +using firebase::firestore::FirestoreErrorCode; using firebase::firestore::api::DocumentReference; +using firebase::firestore::api::DocumentSnapshot; using firebase::firestore::auth::CredentialsProvider; using firebase::firestore::auth::User; using firebase::firestore::core::DatabaseInfo; using firebase::firestore::core::ViewSnapshot; -using firebase::firestore::core::ViewSnapshotHandler; using firebase::firestore::local::LruParams; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::DocumentKeySet; @@ -73,6 +77,9 @@ using firebase::firestore::util::AsyncQueue; using firebase::firestore::util::DelayedOperation; using firebase::firestore::util::Executor; +using firebase::firestore::util::Status; +using firebase::firestore::util::StatusOr; +using firebase::firestore::util::StatusOrCallback; using firebase::firestore::util::TimerId; NS_ASSUME_NONNULL_BEGIN @@ -304,58 +311,45 @@ - (void)shutdownWithCompletion:(nullable FSTVoidErrorBlock)completion { }); } -- (FSTQueryListener *)listenToQuery:(FSTQuery *)query - options:(FSTListenOptions *)options - viewSnapshotHandler:(ViewSnapshotHandler &&)viewSnapshotHandler { - FSTQueryListener *listener = - [[FSTQueryListener alloc] initWithQuery:query - options:options - viewSnapshotHandler:std::move(viewSnapshotHandler)]; +- (std::shared_ptr)listenToQuery:(FSTQuery *)query + options:(ListenOptions)options + listener:(ViewSnapshot::SharedListener &&)listener { + auto query_listener = QueryListener::Create(query, std::move(options), std::move(listener)); - _workerQueue->Enqueue([self, listener] { [self.eventManager addListener:listener]; }); + _workerQueue->Enqueue([self, query_listener] { [self.eventManager addListener:query_listener]; }); - return listener; + return query_listener; } -- (void)removeListener:(FSTQueryListener *)listener { +- (void)removeListener:(const std::shared_ptr &)listener { _workerQueue->Enqueue([self, listener] { [self.eventManager removeListener:listener]; }); } - (void)getDocumentFromLocalCache:(const DocumentReference &)doc - completion:(void (^)(FIRDocumentSnapshot *_Nullable document, - NSError *_Nullable error))completion { - _workerQueue->Enqueue([self, doc, completion] { + completion:(DocumentSnapshot::Listener &&)completion { + auto shared_completion = absl::ShareUniquePtr(std::move(completion)); + _workerQueue->Enqueue([self, doc, shared_completion] { FSTMaybeDocument *maybeDoc = [self.localStore readDocument:doc.key()]; - FIRDocumentSnapshot *_Nullable result = nil; - NSError *_Nullable error = nil; + StatusOr maybe_snapshot; if ([maybeDoc isKindOfClass:[FSTDocument class]]) { FSTDocument *document = (FSTDocument *)maybeDoc; - result = [FIRDocumentSnapshot snapshotWithFirestore:doc.firestore() - documentKey:doc.key() - document:document - fromCache:YES - hasPendingWrites:document.hasLocalMutations]; + maybe_snapshot = DocumentSnapshot{doc.firestore(), doc.key(), document, + /*from_cache=*/true, + /*has_pending_writes=*/document.hasLocalMutations}; } else if ([maybeDoc isKindOfClass:[FSTDeletedDocument class]]) { - result = [FIRDocumentSnapshot snapshotWithFirestore:doc.firestore() - documentKey:doc.key() - document:nil - fromCache:YES - hasPendingWrites:NO]; + maybe_snapshot = DocumentSnapshot{doc.firestore(), doc.key(), nil, + /*from_cache=*/true, + /*has_pending_writes=*/false}; } else { - error = [NSError errorWithDomain:FIRFirestoreErrorDomain - code:FIRFirestoreErrorCodeUnavailable - userInfo:@{ - NSLocalizedDescriptionKey : - @"Failed to get document from cache. (However, this document " - @"may exist on the server. Run again without setting source to " - @"FIRFirestoreSourceCache to attempt to retrieve the document " - @"from the server.)", - }]; + maybe_snapshot = Status{FirestoreErrorCode::Unavailable, + "Failed to get document from cache. (However, this document " + "may exist on the server. Run again without setting source to " + "FirestoreSourceCache to attempt to retrieve the document "}; } - if (completion) { - self->_userExecutor->Execute([=] { completion(result, error); }); + if (shared_completion) { + self->_userExecutor->Execute([=] { shared_completion->OnEvent(std::move(maybe_snapshot)); }); } }); } @@ -375,14 +369,12 @@ - (void)getDocumentsFromLocalCache:(FIRQuery *)query HARD_ASSERT(viewChange.snapshot.has_value(), "Expected a snapshot"); ViewSnapshot snapshot = std::move(viewChange.snapshot).value(); - FIRSnapshotMetadata *metadata = - [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:snapshot.has_pending_writes() - fromCache:snapshot.from_cache()]; - - FIRQuerySnapshot *result = [FIRQuerySnapshot snapshotWithFirestore:query.firestore - originalQuery:query.query - snapshot:std::move(snapshot) - metadata:metadata]; + SnapshotMetadata metadata(snapshot.has_pending_writes(), snapshot.from_cache()); + + FIRQuerySnapshot *result = [[FIRQuerySnapshot alloc] initWithFirestore:query.firestore.wrapped + originalQuery:query.query + snapshot:std::move(snapshot) + metadata:std::move(metadata)]; if (completion) { self->_userExecutor->Execute([=] { completion(result, nil); }); diff --git a/Firestore/Source/Core/FSTQuery.h b/Firestore/Source/Core/FSTQuery.h index 17c781b2838..91268772e4c 100644 --- a/Firestore/Source/Core/FSTQuery.h +++ b/Firestore/Source/Core/FSTQuery.h @@ -16,26 +16,16 @@ #import +#include "Firestore/core/src/firebase/firestore/core/filter.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" @class FSTDocument; @class FSTFieldValue; -NS_ASSUME_NONNULL_BEGIN +using firebase::firestore::core::Filter; -/** - * FSTRelationFilterOperator is a value relation operator that can be used to filter documents. - * It is similar to NSPredicateOperatorType, but only has operators supported by Firestore. - */ -typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) { - FSTRelationFilterOperatorLessThan = 0, - FSTRelationFilterOperatorLessThanOrEqual, - FSTRelationFilterOperatorEqual, - FSTRelationFilterOperatorGreaterThanOrEqual, - FSTRelationFilterOperatorGreaterThan, - FSTRelationFilterOperatorArrayContains, -}; +NS_ASSUME_NONNULL_BEGIN /** Interface used for all query filters. */ @interface FSTFilter : NSObject @@ -43,13 +33,13 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) { /** * Creates a filter for the provided path, operator, and value. * - * Note that if the relational operator is FSTRelationFilterOperatorEqual and + * Note that if the relational operator is Filter::Operator::Equal and * the value is [FSTNullValue nullValue] or [FSTDoubleValue nanValue], this * will return the appropriate FSTNullFilter or FSTNanFilter class instead of a * FSTRelationFilter. */ + (instancetype)filterWithField:(const firebase::firestore::model::FieldPath &)field - filterOperator:(FSTRelationFilterOperator)op + filterOperator:(Filter::Operator)op value:(FSTFieldValue *)value; /** Returns the field the Filter operates over. Abstract method. */ @@ -78,7 +68,7 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) { * @return A new instance of FSTRelationFilter. */ - (instancetype)initWithField:(firebase::firestore::model::FieldPath)field - filterOperator:(FSTRelationFilterOperator)filterOperator + filterOperator:(Filter::Operator)filterOperator value:(FSTFieldValue *)value; - (instancetype)init NS_UNAVAILABLE; @@ -90,7 +80,7 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) { - (const firebase::firestore::model::FieldPath &)field; /** The type of equality/inequality operator to use in the relation. */ -@property(nonatomic, assign, readonly) FSTRelationFilterOperator filterOperator; +@property(nonatomic, assign, readonly) Filter::Operator filterOperator; /** The right hand side of the relation. A constant value to compare to. */ @property(nonatomic, strong, readonly) FSTFieldValue *value; @@ -174,6 +164,7 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) { * Initializes a query with all of its components directly. */ - (instancetype)initWithPath:(firebase::firestore::model::ResourcePath)path + collectionGroup:(nullable NSString *)collectionGroup filterBy:(NSArray *)filters orderBy:(NSArray *)sortOrders limit:(NSInteger)limit @@ -188,6 +179,18 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) { */ + (instancetype)queryWithPath:(firebase::firestore::model::ResourcePath)path; +/** + * Creates and returns a new FSTQuery. + * + * @param path The path to the location to be queried over. Must currently be + * empty in the case of a collection group query. + * @param collectionGroup The collection group to be queried over. nil if this + * is not a collection group query. + * @return A new instance of FSTQuery. + */ ++ (instancetype)queryWithPath:(firebase::firestore::model::ResourcePath)path + collectionGroup:(nullable NSString *)collectionGroup; + /** * Returns the list of ordering constraints that were explicitly requested on the query by the * user. @@ -244,9 +247,19 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) { */ - (instancetype)queryByAddingEndAt:(FSTBound *)bound; +/** + * Helper to convert a collection group query into a collection query at a specific path. This is + * used when executing collection group queries, since we have to split the query into a set of + * collection queries at multiple paths. + */ +- (instancetype)collectionQueryAtPath:(firebase::firestore::model::ResourcePath)path; + /** Returns YES if the receiver is query for a specific document. */ - (BOOL)isDocumentQuery; +/** Returns YES if the receiver is a collection group query. */ +- (BOOL)isCollectionGroupQuery; + /** Returns YES if the @a document matches the constraints of the receiver. */ - (BOOL)matchesDocument:(FSTDocument *)document; @@ -266,6 +279,9 @@ typedef NS_ENUM(NSInteger, FSTRelationFilterOperator) { /** The base path of the query. */ - (const firebase::firestore::model::ResourcePath &)path; +/** The collection group of the query. */ +@property(nonatomic, nullable, strong, readonly) NSString *collectionGroup; + /** The filters on the documents returned by the query. */ @property(nonatomic, strong, readonly) NSArray *filters; diff --git a/Firestore/Source/Core/FSTQuery.mm b/Firestore/Source/Core/FSTQuery.mm index 76598d9c0d5..9304a2df0ff 100644 --- a/Firestore/Source/Core/FSTQuery.mm +++ b/Firestore/Source/Core/FSTQuery.mm @@ -24,23 +24,28 @@ #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTFieldValue.h" #import "Firestore/Source/Util/FSTClasses.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" +#include "Firestore/core/src/firebase/firestore/core/filter.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/hashing.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; +using firebase::firestore::api::ThrowInvalidArgument; +using firebase::firestore::core::Filter; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldPath; +using firebase::firestore::model::FieldValue; using firebase::firestore::model::ResourcePath; NS_ASSUME_NONNULL_BEGIN -#pragma mark - FSTRelationFilterOperator functions +#pragma mark - Filter::Operator functions /** * Returns the reverse order (i.e. Ascending => Descending) etc. @@ -49,41 +54,38 @@ static constexpr NSComparisonResult ReverseOrder(NSComparisonResult result) { return static_cast(-static_cast(result)); } -NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOperator) { +NSString *FSTStringFromQueryRelationOperator(Filter::Operator filterOperator) { switch (filterOperator) { - case FSTRelationFilterOperatorLessThan: + case Filter::Operator::LessThan: return @"<"; - case FSTRelationFilterOperatorLessThanOrEqual: + case Filter::Operator::LessThanOrEqual: return @"<="; - case FSTRelationFilterOperatorEqual: + case Filter::Operator::Equal: return @"=="; - case FSTRelationFilterOperatorGreaterThanOrEqual: + case Filter::Operator::GreaterThanOrEqual: return @">="; - case FSTRelationFilterOperatorGreaterThan: + case Filter::Operator::GreaterThan: return @">"; - case FSTRelationFilterOperatorArrayContains: + case Filter::Operator::ArrayContains: return @"array_contains"; default: - HARD_FAIL("Unknown FSTRelationFilterOperator %s", filterOperator); + HARD_FAIL("Unknown Filter::Operator %s", filterOperator); } } @implementation FSTFilter + (instancetype)filterWithField:(const FieldPath &)field - filterOperator:(FSTRelationFilterOperator)op + filterOperator:(Filter::Operator)op value:(FSTFieldValue *)value { if ([value isEqual:[FSTNullValue nullValue]]) { - if (op != FSTRelationFilterOperatorEqual) { - FSTThrowInvalidUsage(@"InvalidQueryException", - @"Invalid Query. You can only perform equality comparisons on nil / " - "NSNull."); + if (op != Filter::Operator::Equal) { + ThrowInvalidArgument("Invalid Query. Nil and NSNull only support equality comparisons."); } return [[FSTNullFilter alloc] initWithField:field]; } else if ([value isEqual:[FSTDoubleValue nanValue]]) { - if (op != FSTRelationFilterOperatorEqual) { - FSTThrowInvalidUsage(@"InvalidQueryException", - @"Invalid Query. You can only perform equality comparisons on NaN."); + if (op != Filter::Operator::Equal) { + ThrowInvalidArgument("Invalid Query. NaN only supports equality comparisons."); } return [[FSTNanFilter alloc] initWithField:field]; } else { @@ -120,7 +122,7 @@ @interface FSTRelationFilter () { * @param value A constant value to compare @a field to. The RHS of the expression. */ - (instancetype)initWithField:(FieldPath)field - filterOperator:(FSTRelationFilterOperator)filterOperator + filterOperator:(Filter::Operator)filterOperator value:(FSTFieldValue *)value NS_DESIGNATED_INITIALIZER; /** Returns YES if @a document matches the receiver's constraint. */ @@ -139,7 +141,7 @@ @implementation FSTRelationFilter #pragma mark - Constructor methods - (instancetype)initWithField:(FieldPath)field - filterOperator:(FSTRelationFilterOperator)filterOperator + filterOperator:(Filter::Operator)filterOperator value:(FSTFieldValue *)value { self = [super init]; if (self) { @@ -153,8 +155,8 @@ - (instancetype)initWithField:(FieldPath)field #pragma mark - Public Methods - (BOOL)isInequality { - return self.filterOperator != FSTRelationFilterOperatorEqual && - self.filterOperator != FSTRelationFilterOperatorArrayContains; + return self.filterOperator != Filter::Operator::Equal && + self.filterOperator != Filter::Operator::ArrayContains; } - (const firebase::firestore::model::FieldPath &)field { @@ -183,9 +185,9 @@ - (BOOL)isEqual:(id)other { - (BOOL)matchesDocument:(FSTDocument *)document { if (_field.IsKeyFieldPath()) { - HARD_ASSERT([self.value isKindOfClass:[FSTReferenceValue class]], + HARD_ASSERT(self.value.type == FieldValue::Type::Reference, "Comparing on key, but filter value not a FSTReferenceValue."); - HARD_ASSERT(self.filterOperator != FSTRelationFilterOperatorArrayContains, + HARD_ASSERT(self.filterOperator != Filter::Operator::ArrayContains, "arrayContains queries don't make sense on document keys."); FSTReferenceValue *refValue = (FSTReferenceValue *)self.value; NSComparisonResult comparison = CompareKeys(document.key, refValue.value.key); @@ -217,7 +219,7 @@ - (BOOL)isEqualToFilter:(FSTRelationFilter *)other { /** Returns YES if receiver is true with the given value as its LHS. */ - (BOOL)matchesValue:(FSTFieldValue *)other { - if (self.filterOperator == FSTRelationFilterOperatorArrayContains) { + if (self.filterOperator == Filter::Operator::ArrayContains) { if ([other isMemberOfClass:[FSTArrayValue class]]) { FSTArrayValue *arrayValue = (FSTArrayValue *)other; return [arrayValue.internalValue containsObject:self.value]; @@ -234,15 +236,15 @@ - (BOOL)matchesValue:(FSTFieldValue *)other { - (BOOL)matchesComparison:(NSComparisonResult)comparison { switch (self.filterOperator) { - case FSTRelationFilterOperatorLessThan: + case Filter::Operator::LessThan: return comparison == NSOrderedAscending; - case FSTRelationFilterOperatorLessThanOrEqual: + case Filter::Operator::LessThanOrEqual: return comparison == NSOrderedAscending || comparison == NSOrderedSame; - case FSTRelationFilterOperatorEqual: + case Filter::Operator::Equal: return comparison == NSOrderedSame; - case FSTRelationFilterOperatorGreaterThanOrEqual: + case Filter::Operator::GreaterThanOrEqual: return comparison == NSOrderedDescending || comparison == NSOrderedSame; - case FSTRelationFilterOperatorGreaterThan: + case Filter::Operator::GreaterThan: return comparison == NSOrderedDescending; default: HARD_FAIL("Unknown operator: %s", self.filterOperator); @@ -472,7 +474,7 @@ - (BOOL)sortsBeforeDocument:(FSTDocument *)document FSTSortOrder *sortOrderComponent = sortOrder[idx]; NSComparisonResult comparison; if (sortOrderComponent.field == FieldPath::KeyFieldPath()) { - HARD_ASSERT([fieldValue isKindOfClass:[FSTReferenceValue class]], + HARD_ASSERT(fieldValue.type == FieldValue::Type::Reference, "FSTBound has a non-key value where the key path is being used %s", fieldValue); FSTReferenceValue *refValue = (FSTReferenceValue *)fieldValue; comparison = CompareKeys(refValue.value.key, document.key); @@ -535,21 +537,6 @@ @interface FSTQuery () { ResourcePath _path; } -/** - * Initializes the receiver with the given query constraints. - * - * @param path The base path of the query. - * @param filters Filters specify which documents to include in the results. - * @param sortOrders The fields and directions to sort the results. - * @param limit If not NSNotFound, only this many results will be returned. - */ -- (instancetype)initWithPath:(ResourcePath)path - filterBy:(NSArray *)filters - orderBy:(NSArray *)sortOrders - limit:(NSInteger)limit - startAt:(nullable FSTBound *)startAtBound - endAt:(nullable FSTBound *)endAtBound NS_DESIGNATED_INITIALIZER; - /** A list of fields given to sort by. This does not include the implicit key sort at the end. */ @property(nonatomic, strong, readonly) NSArray *explicitSortOrders; @@ -563,7 +550,13 @@ @implementation FSTQuery #pragma mark - Constructors + (instancetype)queryWithPath:(ResourcePath)path { + return [FSTQuery queryWithPath:std::move(path) collectionGroup:nil]; +} + ++ (instancetype)queryWithPath:(ResourcePath)path + collectionGroup:(nullable NSString *)collectionGroup { return [[FSTQuery alloc] initWithPath:std::move(path) + collectionGroup:collectionGroup filterBy:@[] orderBy:@[] limit:NSNotFound @@ -572,6 +565,7 @@ + (instancetype)queryWithPath:(ResourcePath)path { } - (instancetype)initWithPath:(ResourcePath)path + collectionGroup:(nullable NSString *)collectionGroup filterBy:(NSArray *)filters orderBy:(NSArray *)sortOrders limit:(NSInteger)limit @@ -579,6 +573,7 @@ - (instancetype)initWithPath:(ResourcePath)path endAt:(nullable FSTBound *)endAtBound { if (self = [super init]) { _path = std::move(path); + _collectionGroup = collectionGroup; _filters = filters; _explicitSortOrders = sortOrders; _limit = limit; @@ -662,7 +657,7 @@ - (NSArray *)sortOrders { } - (instancetype)queryByAddingFilter:(FSTFilter *)filter { - HARD_ASSERT(!DocumentKey::IsDocumentKey(_path), "No filtering allowed for document query"); + HARD_ASSERT(![self isDocumentQuery], "No filtering allowed for document query"); const FieldPath *newInequalityField = nullptr; if ([filter isKindOfClass:[FSTRelationFilter class]] && @@ -675,6 +670,7 @@ - (instancetype)queryByAddingFilter:(FSTFilter *)filter { "Query must only have one inequality field."); return [[FSTQuery alloc] initWithPath:self.path + collectionGroup:self.collectionGroup filterBy:[self.filters arrayByAddingObject:filter] orderBy:self.explicitSortOrders limit:self.limit @@ -683,10 +679,11 @@ - (instancetype)queryByAddingFilter:(FSTFilter *)filter { } - (instancetype)queryByAddingSortOrder:(FSTSortOrder *)sortOrder { - HARD_ASSERT(!DocumentKey::IsDocumentKey(_path), "No ordering is allowed for a document query."); + HARD_ASSERT(![self isDocumentQuery], "No ordering is allowed for a document query."); // TODO(klimt): Validate that the same key isn't added twice. return [[FSTQuery alloc] initWithPath:self.path + collectionGroup:self.collectionGroup filterBy:self.filters orderBy:[self.explicitSortOrders arrayByAddingObject:sortOrder] limit:self.limit @@ -696,6 +693,7 @@ - (instancetype)queryByAddingSortOrder:(FSTSortOrder *)sortOrder { - (instancetype)queryBySettingLimit:(NSInteger)limit { return [[FSTQuery alloc] initWithPath:self.path + collectionGroup:self.collectionGroup filterBy:self.filters orderBy:self.explicitSortOrders limit:limit @@ -705,6 +703,7 @@ - (instancetype)queryBySettingLimit:(NSInteger)limit { - (instancetype)queryByAddingStartAt:(FSTBound *)bound { return [[FSTQuery alloc] initWithPath:self.path + collectionGroup:self.collectionGroup filterBy:self.filters orderBy:self.explicitSortOrders limit:self.limit @@ -714,6 +713,7 @@ - (instancetype)queryByAddingStartAt:(FSTBound *)bound { - (instancetype)queryByAddingEndAt:(FSTBound *)bound { return [[FSTQuery alloc] initWithPath:self.path + collectionGroup:self.collectionGroup filterBy:self.filters orderBy:self.explicitSortOrders limit:self.limit @@ -721,13 +721,28 @@ - (instancetype)queryByAddingEndAt:(FSTBound *)bound { endAt:bound]; } +- (instancetype)collectionQueryAtPath:(firebase::firestore::model::ResourcePath)path { + return [[FSTQuery alloc] initWithPath:path + collectionGroup:nil + filterBy:self.filters + orderBy:self.explicitSortOrders + limit:self.limit + startAt:self.startAt + endAt:self.endAt]; +} + - (BOOL)isDocumentQuery { - return DocumentKey::IsDocumentKey(_path) && self.filters.count == 0; + return DocumentKey::IsDocumentKey(_path) && !self.collectionGroup && self.filters.count == 0; +} + +- (BOOL)isCollectionGroupQuery { + return self.collectionGroup != nil; } - (BOOL)matchesDocument:(FSTDocument *)document { - return [self pathMatchesDocument:document] && [self orderByMatchesDocument:document] && - [self filtersMatchDocument:document] && [self boundsMatchDocument:document]; + return [self pathAndCollectionGroupMatchDocument:document] && + [self orderByMatchesDocument:document] && [self filtersMatchDocument:document] && + [self boundsMatchDocument:document]; } - (NSComparator)comparator { @@ -758,7 +773,7 @@ - (nullable const FieldPath *)inequalityFilterField { - (BOOL)hasArrayContainsFilter { for (FSTFilter *filter in self.filters) { if ([filter isKindOfClass:[FSTRelationFilter class]] && - ((FSTRelationFilter *)filter).filterOperator == FSTRelationFilterOperatorArrayContains) { + ((FSTRelationFilter *)filter).filterOperator == Filter::Operator::ArrayContains) { return YES; } } @@ -787,6 +802,10 @@ - (NSString *)canonicalID { NSMutableString *canonicalID = [NSMutableString string]; [canonicalID appendFormat:@"%s", _path.CanonicalString().c_str()]; + if (self.collectionGroup) { + [canonicalID appendFormat:@"|cg:%@", self.collectionGroup]; + } + // Add filters. [canonicalID appendString:@"|f:"]; for (FSTFilter *predicate in self.filters) { @@ -819,16 +838,24 @@ - (NSString *)canonicalID { #pragma mark - Private methods - (BOOL)isEqualToQuery:(FSTQuery *)other { - return self.path == other.path && self.limit == other.limit && - [self.filters isEqual:other.filters] && [self.sortOrders isEqual:other.sortOrders] && + return self.path == other.path && + (self.collectionGroup == other.collectionGroup || + [self.collectionGroup isEqual:other.collectionGroup]) && + self.limit == other.limit && [self.filters isEqual:other.filters] && + [self.sortOrders isEqual:other.sortOrders] && (self.startAt == other.startAt || [self.startAt isEqual:other.startAt]) && (self.endAt == other.endAt || [self.endAt isEqual:other.endAt]); } -/* Returns YES if the document matches the path for the receiver. */ -- (BOOL)pathMatchesDocument:(FSTDocument *)document { +/* Returns YES if the document matches the path and collection group for the receiver. */ +- (BOOL)pathAndCollectionGroupMatchDocument:(FSTDocument *)document { const ResourcePath &documentPath = document.key.path(); - if (DocumentKey::IsDocumentKey(_path)) { + if (self.collectionGroup) { + // NOTE: self.path is currently always empty since we don't expose Collection Group queries + // rooted at a document path yet. + return document.key.HasCollectionId(util::MakeString(self.collectionGroup)) && + self.path.IsPrefixOf(documentPath); + } else if (DocumentKey::IsDocumentKey(_path)) { // Exact match for document queries. return self.path == documentPath; } else { diff --git a/Firestore/Source/Core/FSTSyncEngine.mm b/Firestore/Source/Core/FSTSyncEngine.mm index 728933b425d..b00982fea36 100644 --- a/Firestore/Source/Core/FSTSyncEngine.mm +++ b/Firestore/Source/Core/FSTSyncEngine.mm @@ -31,7 +31,6 @@ #import "Firestore/Source/Local/FSTLocalWriteResult.h" #import "Firestore/Source/Local/FSTQueryData.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Source/Model/FSTMutationBatch.h" #include "Firestore/core/src/firebase/firestore/auth/user.h" @@ -41,6 +40,7 @@ #include "Firestore/core/src/firebase/firestore/local/reference_set.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/document_map.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "Firestore/core/src/firebase/firestore/remote/remote_event.h" #include "Firestore/core/src/firebase/firestore/util/error_apple.h" diff --git a/Firestore/Source/Core/FSTView.h b/Firestore/Source/Core/FSTView.h index f6048f9d7ca..aed87321503 100644 --- a/Firestore/Source/Core/FSTView.h +++ b/Firestore/Source/Core/FSTView.h @@ -34,7 +34,6 @@ class TargetChange; } // namespace firestore } // namespace firebase -@class FSTDocumentSet; @class FSTQuery; NS_ASSUME_NONNULL_BEGIN @@ -49,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN - (const firebase::firestore::model::DocumentKeySet &)mutatedKeys; /** The new set of docs that should be in the view. */ -@property(nonatomic, strong, readonly) FSTDocumentSet *documentSet; +- (const firebase::firestore::model::DocumentSet &)documentSet; /** The diff of this these docs with the previous set of docs. */ - (const firebase::firestore::core::DocumentViewChangeSet &)changeSet; diff --git a/Firestore/Source/Core/FSTView.mm b/Firestore/Source/Core/FSTView.mm index f839a5682b8..079c547ad27 100644 --- a/Firestore/Source/Core/FSTView.mm +++ b/Firestore/Source/Core/FSTView.mm @@ -22,12 +22,14 @@ #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Source/Model/FSTFieldValue.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/remote/remote_event.h" +#include "Firestore/core/src/firebase/firestore/util/delayed_constructor.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" using firebase::firestore::core::DocumentViewChange; @@ -36,9 +38,11 @@ using firebase::firestore::core::ViewSnapshot; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentSet; using firebase::firestore::model::MaybeDocumentMap; using firebase::firestore::model::OnlineState; using firebase::firestore::remote::TargetChange; +using firebase::firestore::util::DelayedConstructor; NS_ASSUME_NONNULL_BEGIN @@ -68,7 +72,7 @@ int GetDocumentViewChangeTypePosition(DocumentViewChange::Type changeType) { /** The result of applying a set of doc changes to a view. */ @interface FSTViewDocumentChanges () -- (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet +- (instancetype)initWithDocumentSet:(DocumentSet)documentSet changeSet:(DocumentViewChangeSet &&)changeSet needsRefill:(BOOL)needsRefill mutatedKeys:(DocumentKeySet)mutatedKeys NS_DESIGNATED_INITIALIZER; @@ -76,17 +80,18 @@ - (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet @end @implementation FSTViewDocumentChanges { + DelayedConstructor _documentSet; DocumentKeySet _mutatedKeys; DocumentViewChangeSet _changeSet; } -- (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet +- (instancetype)initWithDocumentSet:(DocumentSet)documentSet changeSet:(DocumentViewChangeSet &&)changeSet needsRefill:(BOOL)needsRefill mutatedKeys:(DocumentKeySet)mutatedKeys { self = [super init]; if (self) { - _documentSet = documentSet; + _documentSet.Init(std::move(documentSet)); _changeSet = std::move(changeSet); _needsRefill = needsRefill; _mutatedKeys = std::move(mutatedKeys); @@ -98,6 +103,10 @@ - (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet return _mutatedKeys; } +- (const firebase::firestore::model::DocumentSet &)documentSet { + return *_documentSet; +} + - (const firebase::firestore::core::DocumentViewChangeSet &)changeSet { return _changeSet; } @@ -208,11 +217,11 @@ @interface FSTView () */ @property(nonatomic, assign, getter=isCurrent) BOOL current; -@property(nonatomic, strong) FSTDocumentSet *documentSet; - @end @implementation FSTView { + DelayedConstructor _documentSet; + /** Documents included in the remote target. */ DocumentKeySet _syncedDocuments; @@ -227,7 +236,7 @@ - (instancetype)initWithQuery:(FSTQuery *)query remoteDocuments:(DocumentKeySet) self = [super init]; if (self) { _query = query; - _documentSet = [FSTDocumentSet documentSetWithComparator:query.comparator]; + _documentSet.Init(query.comparator); _syncedDocuments = std::move(remoteDocuments); } return self; @@ -248,11 +257,11 @@ - (FSTViewDocumentChanges *)computeChangesWithDocuments:(const MaybeDocumentMap if (previousChanges) { changeSet = previousChanges.changeSet; } - FSTDocumentSet *oldDocumentSet = previousChanges ? previousChanges.documentSet : self.documentSet; + DocumentSet oldDocumentSet = previousChanges ? previousChanges.documentSet : *_documentSet; DocumentKeySet newMutatedKeys = previousChanges ? previousChanges.mutatedKeys : _mutatedKeys; DocumentKeySet oldMutatedKeys = _mutatedKeys; - FSTDocumentSet *newDocumentSet = oldDocumentSet; + DocumentSet newDocumentSet = oldDocumentSet; BOOL needsRefill = NO; // Track the last doc in a (full) limit. This is necessary, because some update (a delete, or an @@ -264,14 +273,15 @@ - (FSTViewDocumentChanges *)computeChangesWithDocuments:(const MaybeDocumentMap // Note that this should never get used in a refill (when previousChanges is set), because there // will only be adds -- no deletes or updates. FSTDocument *_Nullable lastDocInLimit = - (self.query.limit && oldDocumentSet.count == self.query.limit) ? oldDocumentSet.lastDocument - : nil; + (self.query.limit != NSNotFound && oldDocumentSet.size() == self.query.limit) + ? oldDocumentSet.GetLastDocument() + : nil; for (const auto &kv : docChanges) { const DocumentKey &key = kv.first; FSTMaybeDocument *maybeNewDoc = kv.second; - FSTDocument *_Nullable oldDoc = [oldDocumentSet documentForKey:key]; + FSTDocument *_Nullable oldDoc = oldDocumentSet.GetDocument(key); FSTDocument *_Nullable newDoc = nil; if ([maybeNewDoc isKindOfClass:[FSTDocument class]]) { newDoc = (FSTDocument *)maybeNewDoc; @@ -328,23 +338,23 @@ - (FSTViewDocumentChanges *)computeChangesWithDocuments:(const MaybeDocumentMap if (changeApplied) { if (newDoc) { - newDocumentSet = [newDocumentSet documentSetByAddingDocument:newDoc]; + newDocumentSet = newDocumentSet.insert(newDoc); if (newDoc.hasLocalMutations) { newMutatedKeys = newMutatedKeys.insert(key); } else { newMutatedKeys = newMutatedKeys.erase(key); } } else { - newDocumentSet = [newDocumentSet documentSetByRemovingKey:key]; + newDocumentSet = newDocumentSet.erase(key); newMutatedKeys = newMutatedKeys.erase(key); } } } - if (self.query.limit) { - for (long i = newDocumentSet.count - self.query.limit; i > 0; --i) { - FSTDocument *oldDoc = [newDocumentSet lastDocument]; - newDocumentSet = [newDocumentSet documentSetByRemovingKey:oldDoc.key]; + if (self.query.limit != NSNotFound && newDocumentSet.size() > self.query.limit) { + for (size_t i = newDocumentSet.size() - self.query.limit; i > 0; --i) { + FSTDocument *oldDoc = newDocumentSet.GetLastDocument(); + newDocumentSet = newDocumentSet.erase(oldDoc.key); newMutatedKeys = newMutatedKeys.erase(oldDoc.key); changeSet.AddChange(DocumentViewChange{oldDoc, DocumentViewChange::Type::kRemoved}); } @@ -353,7 +363,7 @@ - (FSTViewDocumentChanges *)computeChangesWithDocuments:(const MaybeDocumentMap HARD_ASSERT(!needsRefill || !previousChanges, "View was refilled using docs that themselves needed refilling."); - return [[FSTViewDocumentChanges alloc] initWithDocumentSet:newDocumentSet + return [[FSTViewDocumentChanges alloc] initWithDocumentSet:std::move(newDocumentSet) changeSet:std::move(changeSet) needsRefill:needsRefill mutatedKeys:newMutatedKeys]; @@ -377,8 +387,8 @@ - (FSTViewChange *)applyChangesToDocuments:(FSTViewDocumentChanges *)docChanges targetChange:(const absl::optional &)targetChange { HARD_ASSERT(!docChanges.needsRefill, "Cannot apply changes that need a refill"); - FSTDocumentSet *oldDocuments = self.documentSet; - self.documentSet = docChanges.documentSet; + DocumentSet oldDocuments = *_documentSet; + *_documentSet = docChanges.documentSet; _mutatedKeys = docChanges.mutatedKeys; // Sort changes based on type and query comparator. @@ -424,7 +434,7 @@ - (FSTViewChange *)applyChangedOnlineState:(OnlineState)onlineState { // that sets `current` back to YES once the client is back online. self.current = NO; return [self applyChangesToDocuments:[[FSTViewDocumentChanges alloc] - initWithDocumentSet:self.documentSet + initWithDocumentSet:*_documentSet changeSet:DocumentViewChangeSet {} needsRefill:NO mutatedKeys:_mutatedKeys]]; @@ -443,14 +453,14 @@ - (BOOL)shouldBeLimboDocumentKey:(const DocumentKey &)key { return NO; } // The local store doesn't think it's a result, so it shouldn't be in limbo. - if (![self.documentSet containsKey:key]) { + if (!_documentSet->ContainsKey(key)) { return NO; } // If there are local changes to the doc, they might explain why the server doesn't know that it's // part of the query. So don't put it in limbo. // TODO(klimt): Ideally, we would only consider changes that might actually affect this specific // query. - if ([self.documentSet documentForKey:key].hasLocalMutations) { + if (_documentSet->GetDocument(key).hasLocalMutations) { return NO; } // Everything else is in limbo. @@ -489,7 +499,7 @@ - (void)applyTargetChange:(const absl::optional &)maybeTargetChang // TODO(klimt): Do this incrementally so that it's not quadratic when updating many documents. DocumentKeySet oldLimboDocuments = std::move(_limboDocuments); _limboDocuments = DocumentKeySet{}; - for (FSTDocument *doc in self.documentSet.documentEnumerator) { + for (FSTDocument *doc : *_documentSet) { if ([self shouldBeLimboDocumentKey:doc.key]) { _limboDocuments = _limboDocuments.insert(doc.key); } diff --git a/Firestore/Source/Local/FSTLRUGarbageCollector.h b/Firestore/Source/Local/FSTLRUGarbageCollector.h index 0b3cc7b3110..9d59813a94c 100644 --- a/Firestore/Source/Local/FSTLRUGarbageCollector.h +++ b/Firestore/Source/Local/FSTLRUGarbageCollector.h @@ -21,6 +21,8 @@ #import "FIRFirestoreSettings.h" #import "Firestore/Source/Local/FSTQueryData.h" + +#include "Firestore/core/src/firebase/firestore/local/query_cache.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/types.h" @@ -79,15 +81,13 @@ struct LruResults { * Enumerates all the targets that the delegate is aware of. This is typically all of the targets in * an FSTQueryCache. */ -- (void)enumerateTargetsUsingBlock:(void (^)(FSTQueryData *queryData, BOOL *stop))block; +- (void)enumerateTargetsUsingCallback:(const firebase::firestore::local::TargetCallback &)callback; /** * Enumerates all of the outstanding mutations. */ -- (void)enumerateMutationsUsingBlock: - (void (^)(const firebase::firestore::model::DocumentKey &key, - firebase::firestore::model::ListenSequenceNumber sequenceNumber, - BOOL *stop))block; +- (void)enumerateMutationsUsingCallback: + (const firebase::firestore::local::OrphanedDocumentCallback &)callback; /** * Removes all unreferenced documents from the cache that have a sequence number less than or equal diff --git a/Firestore/Source/Local/FSTLRUGarbageCollector.mm b/Firestore/Source/Local/FSTLRUGarbageCollector.mm index b47a833857a..95a86fabb56 100644 --- a/Firestore/Source/Local/FSTLRUGarbageCollector.mm +++ b/Firestore/Source/Local/FSTLRUGarbageCollector.mm @@ -159,14 +159,13 @@ - (ListenSequenceNumber)sequenceNumberForQueryCount:(NSUInteger)queryCount { return kFSTListenSequenceNumberInvalid; } RollingSequenceNumberBuffer buffer(queryCount); - // Pointer is necessary to access stack-allocated buffer from a block. - RollingSequenceNumberBuffer *ptr_to_buffer = &buffer; - [_delegate enumerateTargetsUsingBlock:^(FSTQueryData *queryData, BOOL *stop) { - ptr_to_buffer->AddElement(queryData.sequenceNumber); + + [_delegate enumerateTargetsUsingCallback:[&buffer](FSTQueryData *queryData) { + buffer.AddElement(queryData.sequenceNumber); }]; - [_delegate enumerateMutationsUsingBlock:^(const DocumentKey &docKey, - ListenSequenceNumber sequenceNumber, BOOL *stop) { - ptr_to_buffer->AddElement(sequenceNumber); + [_delegate enumerateMutationsUsingCallback:[&buffer](const DocumentKey &docKey, + ListenSequenceNumber sequenceNumber) { + buffer.AddElement(sequenceNumber); }]; return buffer.max_value(); } diff --git a/Firestore/Source/Local/FSTLevelDB.mm b/Firestore/Source/Local/FSTLevelDB.mm index ae010cfc272..46cb63fadfe 100644 --- a/Firestore/Source/Local/FSTLevelDB.mm +++ b/Firestore/Source/Local/FSTLevelDB.mm @@ -27,6 +27,8 @@ #include "Firestore/core/include/firebase/firestore/firestore_errors.h" #include "Firestore/core/src/firebase/firestore/auth/user.h" #include "Firestore/core/src/firebase/firestore/core/database_info.h" +#include "Firestore/core/src/firebase/firestore/local/index_manager.h" +#include "Firestore/core/src/firebase/firestore/local/leveldb_index_manager.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_key.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_migrations.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_mutation_queue.h" @@ -59,8 +61,10 @@ using firebase::firestore::auth::User; using firebase::firestore::core::DatabaseInfo; using firebase::firestore::local::ConvertStatus; +using firebase::firestore::local::IndexManager; using firebase::firestore::local::LevelDbDocumentMutationKey; using firebase::firestore::local::LevelDbDocumentTargetKey; +using firebase::firestore::local::LevelDbIndexManager; using firebase::firestore::local::LevelDbMigrations; using firebase::firestore::local::LevelDbMutationKey; using firebase::firestore::local::LevelDbMutationQueue; @@ -69,8 +73,10 @@ using firebase::firestore::local::LevelDbTransaction; using firebase::firestore::local::ListenSequence; using firebase::firestore::local::LruParams; +using firebase::firestore::local::OrphanedDocumentCallback; using firebase::firestore::local::ReferenceSet; using firebase::firestore::local::RemoteDocumentCache; +using firebase::firestore::local::TargetCallback; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::ListenSequenceNumber; @@ -206,19 +212,18 @@ - (BOOL)isPinned:(const DocumentKey &)docKey { return NO; } -- (void)enumerateTargetsUsingBlock:(void (^)(FSTQueryData *queryData, BOOL *stop))block { - _db.queryCache->EnumerateTargets(block); +- (void)enumerateTargetsUsingCallback:(const TargetCallback &)callback { + _db.queryCache->EnumerateTargets(callback); } -- (void)enumerateMutationsUsingBlock: - (void (^)(const DocumentKey &key, ListenSequenceNumber sequenceNumber, BOOL *stop))block { - _db.queryCache->EnumerateOrphanedDocuments(block); +- (void)enumerateMutationsUsingCallback:(const OrphanedDocumentCallback &)callback { + _db.queryCache->EnumerateOrphanedDocuments(callback); } - (int)removeOrphanedDocumentsThroughSequenceNumber:(ListenSequenceNumber)upperBound { - __block int count = 0; + int count = 0; _db.queryCache->EnumerateOrphanedDocuments( - ^(const DocumentKey &docKey, ListenSequenceNumber sequenceNumber, BOOL *stop) { + [&count, self, upperBound](const DocumentKey &docKey, ListenSequenceNumber sequenceNumber) { if (sequenceNumber <= upperBound) { if (![self isPinned:docKey]) { count++; @@ -241,9 +246,9 @@ - (int)removeTargetsThroughSequenceNumber:(ListenSequenceNumber)sequenceNumber } - (size_t)sequenceNumberCount { - __block size_t totalCount = _db.queryCache->size(); - [self enumerateMutationsUsingBlock:^(const DocumentKey &key, ListenSequenceNumber sequenceNumber, - BOOL *stop) { + size_t totalCount = _db.queryCache->size(); + [self enumerateMutationsUsingCallback:[&totalCount](const DocumentKey &key, + ListenSequenceNumber sequenceNumber) { totalCount++; }]; return totalCount; @@ -279,6 +284,7 @@ @implementation FSTLevelDB { std::unique_ptr _transaction; std::unique_ptr _ptr; std::unique_ptr _documentCache; + std::unique_ptr _indexManager; FSTTransactionRunner _transactionRunner; FSTLevelDBLRUDelegate *_referenceDelegate; std::unique_ptr _queryCache; @@ -351,6 +357,7 @@ - (instancetype)initWithLevelDB:(std::unique_ptr)db _serializer = serializer; _queryCache = absl::make_unique(self, _serializer); _documentCache = absl::make_unique(self, _serializer); + _indexManager = absl::make_unique(self); _referenceDelegate = [[FSTLevelDBLRUDelegate alloc] initWithPersistence:self lruParams:lruParams]; _transactionRunner.SetBackingPersistence(self); @@ -371,8 +378,8 @@ - (size_t)byteSize { } HARD_ASSERT(iter->status().ok(), "Failed to iterate leveldb directory: %s", iter->status().error_message().c_str()); - HARD_ASSERT(count <= SIZE_MAX, "Overflowed counting bytes cached"); - return count; + HARD_ASSERT(count >= 0 && count <= SIZE_MAX, "Overflowed counting bytes cached"); + return static_cast(count); } - (const std::set &)users { @@ -388,19 +395,22 @@ - (size_t)byteSize { } + (Path)documentsDirectory { -#if TARGET_OS_IPHONE +#if TARGET_OS_IOS NSArray *directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); return Path::FromNSString(directories[0]).AppendUtf8(kReservedPathComponent); +#elif TARGET_OS_TV + NSArray *directories = + NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + return Path::FromNSString(directories[0]).AppendUtf8(kReservedPathComponent); + #elif TARGET_OS_OSX std::string dotPrefixed = absl::StrCat(".", kReservedPathComponent); return Path::FromNSString(NSHomeDirectory()).AppendUtf8(dotPrefixed); #else -#error "local storage on tvOS" - // TODO(mcg): Writing to NSDocumentsDirectory on tvOS will fail; we need to write to Caches - // https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/ +#error "Don't know where to store documents on this platform." #endif } @@ -481,6 +491,10 @@ - (RemoteDocumentCache *)remoteDocumentCache { return _documentCache.get(); } +- (IndexManager *)indexManager { + return _indexManager.get(); +} + - (void)startTransaction:(absl::string_view)label { HARD_ASSERT(_transaction == nullptr, "Starting a transaction while one is already outstanding"); _transaction = absl::make_unique(_ptr.get(), label); diff --git a/Firestore/Source/Local/FSTLocalDocumentsView.h b/Firestore/Source/Local/FSTLocalDocumentsView.h deleted file mode 100644 index 730e0ea47bc..00000000000 --- a/Firestore/Source/Local/FSTLocalDocumentsView.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#include "Firestore/core/src/firebase/firestore/local/mutation_queue.h" -#include "Firestore/core/src/firebase/firestore/local/remote_document_cache.h" -#include "Firestore/core/src/firebase/firestore/model/document_key.h" -#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" -#include "Firestore/core/src/firebase/firestore/model/document_map.h" - -@class FSTMaybeDocument; -@class FSTQuery; - -NS_ASSUME_NONNULL_BEGIN - -/** - * A readonly view of the local state of all documents we're tracking (i.e. we have a cached - * version in remoteDocumentCache or local mutations for the document). The view is computed by - * applying the mutations in the FSTMutationQueue to the FSTRemoteDocumentCache. - */ -@interface FSTLocalDocumentsView : NSObject - -+ (instancetype)viewWithRemoteDocumentCache: - (firebase::firestore::local::RemoteDocumentCache *)remoteDocumentCache - mutationQueue: - (firebase::firestore::local::MutationQueue *)mutationQueue; - -- (instancetype)init __attribute__((unavailable("Use a static constructor"))); - -/** - * Get the local view of the document identified by `key`. - * - * @return Local view of the document or nil if we don't have any cached state for it. - */ -- (nullable FSTMaybeDocument *)documentForKey:(const firebase::firestore::model::DocumentKey &)key; - -/** - * Gets the local view of the documents identified by `keys`. - * - * If we don't have cached state for a document in `keys`, a FSTDeletedDocument will be stored - * for that key in the resulting set. - */ -- (firebase::firestore::model::MaybeDocumentMap)documentsForKeys: - (const firebase::firestore::model::DocumentKeySet &)keys; - -/** - * Similar to `documentsForKeys`, but creates the local view from the given - * `baseDocs` without retrieving documents from the local store. - */ -- (firebase::firestore::model::MaybeDocumentMap)localViewsForDocuments: - (const firebase::firestore::model::MaybeDocumentMap &)baseDocs; - -/** Performs a query against the local view of all documents. */ -- (firebase::firestore::model::DocumentMap)documentsMatchingQuery:(FSTQuery *)query; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Local/FSTLocalDocumentsView.mm b/Firestore/Source/Local/FSTLocalDocumentsView.mm deleted file mode 100644 index a357f5b5f4f..00000000000 --- a/Firestore/Source/Local/FSTLocalDocumentsView.mm +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "Firestore/Source/Local/FSTLocalDocumentsView.h" - -#include - -#import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTMutation.h" -#import "Firestore/Source/Model/FSTMutationBatch.h" - -#include "Firestore/core/src/firebase/firestore/local/mutation_queue.h" -#include "Firestore/core/src/firebase/firestore/local/remote_document_cache.h" -#include "Firestore/core/src/firebase/firestore/model/document_key.h" -#include "Firestore/core/src/firebase/firestore/model/document_map.h" -#include "Firestore/core/src/firebase/firestore/model/resource_path.h" -#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" -#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" - -using firebase::firestore::local::MutationQueue; -using firebase::firestore::local::RemoteDocumentCache; -using firebase::firestore::model::DocumentKey; -using firebase::firestore::model::DocumentKeySet; -using firebase::firestore::model::DocumentMap; -using firebase::firestore::model::MaybeDocumentMap; -using firebase::firestore::model::ResourcePath; -using firebase::firestore::model::SnapshotVersion; - -NS_ASSUME_NONNULL_BEGIN - -@interface FSTLocalDocumentsView () -- (instancetype)initWithRemoteDocumentCache:(RemoteDocumentCache *)remoteDocumentCache - mutationQueue:(MutationQueue *)mutationQueue - NS_DESIGNATED_INITIALIZER; - -@end - -@implementation FSTLocalDocumentsView { - RemoteDocumentCache *_remoteDocumentCache; - MutationQueue *_mutationQueue; -} - -+ (instancetype)viewWithRemoteDocumentCache:(RemoteDocumentCache *)remoteDocumentCache - mutationQueue:(MutationQueue *)mutationQueue { - return [[FSTLocalDocumentsView alloc] initWithRemoteDocumentCache:remoteDocumentCache - mutationQueue:mutationQueue]; -} - -- (instancetype)initWithRemoteDocumentCache:(RemoteDocumentCache *)remoteDocumentCache - mutationQueue:(MutationQueue *)mutationQueue { - if (self = [super init]) { - _remoteDocumentCache = remoteDocumentCache; - _mutationQueue = mutationQueue; - } - return self; -} - -- (nullable FSTMaybeDocument *)documentForKey:(const DocumentKey &)key { - std::vector batches = - _mutationQueue->AllMutationBatchesAffectingDocumentKey(key); - return [self documentForKey:key inBatches:batches]; -} - -// Internal version of documentForKey: which allows reusing `batches`. -- (nullable FSTMaybeDocument *)documentForKey:(const DocumentKey &)key - inBatches:(const std::vector &)batches { - FSTMaybeDocument *_Nullable document = _remoteDocumentCache->Get(key); - for (FSTMutationBatch *batch : batches) { - document = [batch applyToLocalDocument:document documentKey:key]; - } - - return document; -} - -// Returns the view of the given `docs` as they would appear after applying all -// mutations in the given `batches`. -- (MaybeDocumentMap)applyLocalMutationsToDocuments:(const MaybeDocumentMap &)docs - fromBatches: - (const std::vector &)batches { - MaybeDocumentMap results; - - for (const auto &kv : docs) { - const DocumentKey &key = kv.first; - FSTMaybeDocument *localView = kv.second; - for (FSTMutationBatch *batch : batches) { - localView = [batch applyToLocalDocument:localView documentKey:key]; - } - results = results.insert(key, localView); - } - return results; -} - -- (MaybeDocumentMap)documentsForKeys:(const DocumentKeySet &)keys { - MaybeDocumentMap docs = _remoteDocumentCache->GetAll(keys); - return [self localViewsForDocuments:docs]; -} - -/** - * Similar to `documentsForKeys`, but creates the local view from the given - * `baseDocs` without retrieving documents from the local store. - */ -- (MaybeDocumentMap)localViewsForDocuments:(const MaybeDocumentMap &)baseDocs { - MaybeDocumentMap results; - - DocumentKeySet allKeys; - for (const auto &kv : baseDocs) { - allKeys = allKeys.insert(kv.first); - } - std::vector batches = - _mutationQueue->AllMutationBatchesAffectingDocumentKeys(allKeys); - - MaybeDocumentMap docs = [self applyLocalMutationsToDocuments:baseDocs fromBatches:batches]; - - for (const auto &kv : docs) { - const DocumentKey &key = kv.first; - FSTMaybeDocument *maybeDoc = kv.second; - - // TODO(http://b/32275378): Don't conflate missing / deleted. - if (!maybeDoc) { - maybeDoc = [FSTDeletedDocument documentWithKey:key - version:SnapshotVersion::None() - hasCommittedMutations:NO]; - } - results = results.insert(key, maybeDoc); - } - - return results; -} - -- (DocumentMap)documentsMatchingQuery:(FSTQuery *)query { - if (DocumentKey::IsDocumentKey(query.path)) { - return [self documentsMatchingDocumentQuery:query.path]; - } else { - return [self documentsMatchingCollectionQuery:query]; - } -} - -- (DocumentMap)documentsMatchingDocumentQuery:(const ResourcePath &)docPath { - DocumentMap result; - // Just do a simple document lookup. - FSTMaybeDocument *doc = [self documentForKey:DocumentKey{docPath}]; - if ([doc isKindOfClass:[FSTDocument class]]) { - result = result.insert(doc.key, static_cast(doc)); - } - return result; -} - -- (DocumentMap)documentsMatchingCollectionQuery:(FSTQuery *)query { - DocumentMap results = _remoteDocumentCache->GetMatching(query); - // Get locally persisted mutation batches. - std::vector matchingBatches = - _mutationQueue->AllMutationBatchesAffectingQuery(query); - - for (FSTMutationBatch *batch : matchingBatches) { - for (FSTMutation *mutation : [batch mutations]) { - // Only process documents belonging to the collection. - if (!query.path.IsImmediateParentOf(mutation.key.path())) { - continue; - } - - const DocumentKey &key = mutation.key; - // baseDoc may be nil for the documents that weren't yet written to the backend. - FSTMaybeDocument *baseDoc = nil; - auto found = results.underlying_map().find(key); - if (found != results.underlying_map().end()) { - baseDoc = found->second; - } - FSTMaybeDocument *mutatedDoc = [mutation applyToLocalDocument:baseDoc - baseDocument:baseDoc - localWriteTime:batch.localWriteTime]; - - if ([mutatedDoc isKindOfClass:[FSTDocument class]]) { - results = results.insert(key, static_cast(mutatedDoc)); - } else { - results = results.erase(key); - } - } - } - - // Finally, filter out any documents that don't actually match the query. Note that the extra - // reference here prevents ARC from deallocating the initial unfiltered results while we're - // enumerating them. - DocumentMap unfiltered = results; - for (const auto &kv : unfiltered.underlying_map()) { - const DocumentKey &key = kv.first; - FSTDocument *doc = static_cast(kv.second); - if (![query matchesDocument:doc]) { - results = results.erase(key); - } - } - - return results; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Local/FSTLocalStore.mm b/Firestore/Source/Local/FSTLocalStore.mm index 7a6785242f3..97d542bd37d 100644 --- a/Firestore/Source/Local/FSTLocalStore.mm +++ b/Firestore/Source/Local/FSTLocalStore.mm @@ -16,6 +16,7 @@ #import "Firestore/Source/Local/FSTLocalStore.h" +#include #include #include #include @@ -24,7 +25,6 @@ #import "FIRTimestamp.h" #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Local/FSTLRUGarbageCollector.h" -#import "Firestore/Source/Local/FSTLocalDocumentsView.h" #import "Firestore/Source/Local/FSTLocalViewChanges.h" #import "Firestore/Source/Local/FSTLocalWriteResult.h" #import "Firestore/Source/Local/FSTPersistence.h" @@ -36,17 +36,22 @@ #include "Firestore/core/src/firebase/firestore/auth/user.h" #include "Firestore/core/src/firebase/firestore/core/target_id_generator.h" #include "Firestore/core/src/firebase/firestore/immutable/sorted_set.h" +#include "Firestore/core/src/firebase/firestore/local/local_documents_view.h" #include "Firestore/core/src/firebase/firestore/local/mutation_queue.h" #include "Firestore/core/src/firebase/firestore/local/query_cache.h" #include "Firestore/core/src/firebase/firestore/local/reference_set.h" #include "Firestore/core/src/firebase/firestore/local/remote_document_cache.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/document_map.h" #include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "Firestore/core/src/firebase/firestore/remote/remote_event.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/log.h" +#include "absl/memory/memory.h" using firebase::firestore::auth::User; using firebase::firestore::core::TargetIdGenerator; +using firebase::firestore::local::LocalDocumentsView; using firebase::firestore::local::LruResults; using firebase::firestore::local::MutationQueue; using firebase::firestore::local::QueryCache; @@ -82,9 +87,6 @@ @interface FSTLocalStore () /** Manages our in-memory or durable persistence. */ @property(nonatomic, strong, readonly) id persistence; -/** The "local" view of all documents (layering mutationQueue on top of remoteDocumentCache). */ -@property(nonatomic, nullable, strong) FSTLocalDocumentsView *localDocuments; - /** Maps a query to the data about that query. */ @property(nonatomic) QueryCache *queryCache; @@ -99,6 +101,9 @@ @implementation FSTLocalStore { /** The set of all mutations that have been sent but not yet been applied to the backend. */ MutationQueue *_mutationQueue; + /** The "local" view of all documents (layering mutationQueue on top of remoteDocumentCache). */ + std::unique_ptr _localDocuments; + /** The set of document references maintained by any local views. */ ReferenceSet _localViewReferences; @@ -113,8 +118,8 @@ - (instancetype)initWithPersistence:(id)persistence _mutationQueue = [persistence mutationQueueForUser:initialUser]; _remoteDocumentCache = [persistence remoteDocumentCache]; _queryCache = [persistence queryCache]; - _localDocuments = [FSTLocalDocumentsView viewWithRemoteDocumentCache:_remoteDocumentCache - mutationQueue:_mutationQueue]; + _localDocuments = absl::make_unique(_remoteDocumentCache, _mutationQueue, + [_persistence indexManager]); [_persistence.referenceDelegate addInMemoryPins:&_localViewReferences]; _targetIDGenerator = TargetIdGenerator::QueryCacheTargetIdGenerator(0); @@ -139,7 +144,7 @@ - (MaybeDocumentMap)userDidChange:(const User &)user { [&]() -> std::vector { return _mutationQueue->AllMutationBatches(); }); // The old one has a reference to the mutation queue, so nil it out first. - self.localDocuments = nil; + _localDocuments.reset(); _mutationQueue = [self.persistence mutationQueueForUser:user]; [self startMutationQueue]; @@ -148,8 +153,8 @@ - (MaybeDocumentMap)userDidChange:(const User &)user { std::vector newBatches = _mutationQueue->AllMutationBatches(); // Recreate our LocalDocumentsView using the new MutationQueue. - self.localDocuments = [FSTLocalDocumentsView viewWithRemoteDocumentCache:_remoteDocumentCache - mutationQueue:_mutationQueue]; + _localDocuments = absl::make_unique(_remoteDocumentCache, _mutationQueue, + [_persistence indexManager]); // Union the old/new changed keys. DocumentKeySet changedKeys; @@ -162,7 +167,7 @@ - (MaybeDocumentMap)userDidChange:(const User &)user { } // Return the set of all (potentially) changed documents as the result of the user change. - return [self.localDocuments documentsForKeys:changedKeys]; + return _localDocuments->GetDocuments(changedKeys); }); } @@ -176,7 +181,7 @@ - (FSTLocalWriteResult *)locallyWriteMutations:(std::vector &&)mu return self.persistence.run("Locally write mutations", [&]() -> FSTLocalWriteResult * { // Load and apply all existing mutations. This lets us compute the current base state for // all non-idempotent transforms before applying any additional user-provided writes. - MaybeDocumentMap existingDocuments = [self.localDocuments documentsForKeys:keys]; + MaybeDocumentMap existingDocuments = _localDocuments->GetDocuments(keys); // For non-idempotent mutations (such as `FieldValue.increment()`), we record the base // state in a separate patch mutation. This is later used to guarantee consistent values @@ -226,7 +231,7 @@ - (MaybeDocumentMap)acknowledgeBatchWithResult:(FSTMutationBatchResult *)batchRe [self applyBatchResult:batchResult]; _mutationQueue->PerformConsistencyCheck(); - return [self.localDocuments documentsForKeys:batch.keys]; + return _localDocuments->GetDocuments(batch.keys); }); } @@ -238,7 +243,7 @@ - (MaybeDocumentMap)rejectBatchID:(BatchId)batchID { _mutationQueue->RemoveMutationBatch(toReject); _mutationQueue->PerformConsistencyCheck(); - return [self.localDocuments documentsForKeys:toReject.keys]; + return _localDocuments->GetDocuments(toReject.keys); }); } @@ -359,7 +364,7 @@ - (MaybeDocumentMap)applyRemoteEvent:(const RemoteEvent &)remoteEvent { _queryCache->SetLastRemoteSnapshotVersion(remoteVersion); } - return [self.localDocuments localViewsForDocuments:changedDocs]; + return _localDocuments->GetLocalViewOfDocuments(changedDocs); }); } @@ -422,7 +427,7 @@ - (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(BatchId)batchID { - (nullable FSTMaybeDocument *)readDocument:(const DocumentKey &)key { return self.persistence.run("ReadDocument", [&]() -> FSTMaybeDocument *_Nullable { - return [self.localDocuments documentForKey:key]; + return _localDocuments->GetDocument(key); }); } @@ -479,7 +484,7 @@ - (void)releaseQuery:(FSTQuery *)query { - (DocumentMap)executeQuery:(FSTQuery *)query { return self.persistence.run("ExecuteQuery", [&]() -> DocumentMap { - return [self.localDocuments documentsMatchingQuery:query]; + return _localDocuments->GetDocumentsMatchingQuery(query); }); } diff --git a/Firestore/Source/Local/FSTLocalViewChanges.h b/Firestore/Source/Local/FSTLocalViewChanges.h index 47651a0a272..66ec8631194 100644 --- a/Firestore/Source/Local/FSTLocalViewChanges.h +++ b/Firestore/Source/Local/FSTLocalViewChanges.h @@ -20,7 +20,6 @@ #include "Firestore/core/src/firebase/firestore/model/document_key_set.h" #include "Firestore/core/src/firebase/firestore/model/types.h" -@class FSTDocumentSet; @class FSTMutation; @class FSTQuery; diff --git a/Firestore/Source/Local/FSTMemoryPersistence.mm b/Firestore/Source/Local/FSTMemoryPersistence.mm index 6dc197cab32..d60f7e37f69 100644 --- a/Firestore/Source/Local/FSTMemoryPersistence.mm +++ b/Firestore/Source/Local/FSTMemoryPersistence.mm @@ -22,7 +22,9 @@ #include #include "Firestore/core/src/firebase/firestore/auth/user.h" +#include "Firestore/core/src/firebase/firestore/local/index_manager.h" #include "Firestore/core/src/firebase/firestore/local/listen_sequence.h" +#include "Firestore/core/src/firebase/firestore/local/memory_index_manager.h" #include "Firestore/core/src/firebase/firestore/local/memory_mutation_queue.h" #include "Firestore/core/src/firebase/firestore/local/memory_query_cache.h" #include "Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.h" @@ -35,10 +37,12 @@ using firebase::firestore::auth::User; using firebase::firestore::local::ListenSequence; using firebase::firestore::local::LruParams; +using firebase::firestore::local::MemoryIndexManager; using firebase::firestore::local::MemoryMutationQueue; using firebase::firestore::local::MemoryQueryCache; using firebase::firestore::local::MemoryRemoteDocumentCache; using firebase::firestore::local::ReferenceSet; +using firebase::firestore::local::TargetCallback; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::DocumentKeyHash; using firebase::firestore::model::ListenSequenceNumber; @@ -55,6 +59,8 @@ - (MemoryQueryCache *)queryCache; - (MemoryRemoteDocumentCache *)remoteDocumentCache; +- (MemoryIndexManager *)indexManager; + - (MemoryMutationQueue *)mutationQueueForUser:(const User &)user; @property(nonatomic, readonly) MutationQueues &mutationQueues; @@ -78,7 +84,9 @@ @implementation FSTMemoryPersistence { std::unique_ptr _queryCache; /** The RemoteDocumentCache representing the persisted cache of remote documents. */ - MemoryRemoteDocumentCache _remoteDocumentCache; + std::unique_ptr _remoteDocumentCache; + + MemoryIndexManager _indexManager; FSTTransactionRunner _transactionRunner; @@ -105,6 +113,7 @@ + (instancetype)persistenceWithLruParams:(firebase::firestore::local::LruParams) - (instancetype)init { if (self = [super init]) { _queryCache = absl::make_unique(self); + _remoteDocumentCache = absl::make_unique(self); self.started = YES; } return self; @@ -151,7 +160,11 @@ - (MemoryQueryCache *)queryCache { } - (MemoryRemoteDocumentCache *)remoteDocumentCache { - return &_remoteDocumentCache; + return _remoteDocumentCache.get(); +} + +- (MemoryIndexManager *)indexManager { + return &_indexManager; } @end @@ -222,20 +235,19 @@ - (void)commitTransaction { _currentSequenceNumber = kFSTListenSequenceNumberInvalid; } -- (void)enumerateTargetsUsingBlock:(void (^)(FSTQueryData *queryData, BOOL *stop))block { - return _persistence.queryCache->EnumerateTargets(block); +- (void)enumerateTargetsUsingCallback:(const TargetCallback &)callback { + return _persistence.queryCache->EnumerateTargets(callback); } -- (void)enumerateMutationsUsingBlock: - (void (^)(const DocumentKey &key, ListenSequenceNumber sequenceNumber, BOOL *stop))block { - BOOL stop = NO; +- (void)enumerateMutationsUsingCallback: + (const firebase::firestore::local::OrphanedDocumentCallback &)callback { for (const auto &entry : _sequenceNumbers) { ListenSequenceNumber sequenceNumber = entry.second; const DocumentKey &key = entry.first; // Pass in the exact sequence number as the upper bound so we know it won't be pinned by being // too recent. if (![self isPinnedAtSequenceNumber:sequenceNumber document:key]) { - block(key, sequenceNumber, &stop); + callback(key, sequenceNumber); } } } @@ -247,9 +259,9 @@ - (int)removeTargetsThroughSequenceNumber:(ListenSequenceNumber)sequenceNumber } - (size_t)sequenceNumberCount { - __block size_t totalCount = _persistence.queryCache->size(); - [self enumerateMutationsUsingBlock:^(const DocumentKey &key, ListenSequenceNumber sequenceNumber, - BOOL *stop) { + size_t totalCount = _persistence.queryCache->size(); + [self enumerateMutationsUsingCallback:[&totalCount](const DocumentKey &key, + ListenSequenceNumber sequenceNumber) { totalCount++; }]; return totalCount; diff --git a/Firestore/Source/Local/FSTPersistence.h b/Firestore/Source/Local/FSTPersistence.h index 961ad0dcbf7..8595f107a1e 100644 --- a/Firestore/Source/Local/FSTPersistence.h +++ b/Firestore/Source/Local/FSTPersistence.h @@ -17,6 +17,7 @@ #import #include "Firestore/core/src/firebase/firestore/auth/user.h" +#include "Firestore/core/src/firebase/firestore/local/index_manager.h" #include "Firestore/core/src/firebase/firestore/local/mutation_queue.h" #include "Firestore/core/src/firebase/firestore/local/query_cache.h" #include "Firestore/core/src/firebase/firestore/local/reference_set.h" @@ -82,9 +83,12 @@ NS_ASSUME_NONNULL_BEGIN /** Creates an FSTQueryCache representing the persisted cache of queries. */ - (firebase::firestore::local::QueryCache *)queryCache; -/** Creates an FSTRemoteDocumentCache representing the persisted cache of remote documents. */ +/** Creates a RemoteDocumentCache representing the persisted cache of remote documents. */ - (firebase::firestore::local::RemoteDocumentCache *)remoteDocumentCache; +/** Creates an IndexManager that manages our persisted query indexes. */ +- (firebase::firestore::local::IndexManager *)indexManager; + @property(nonatomic, readonly, assign) const FSTTransactionRunner &run; /** diff --git a/Firestore/Source/Model/FSTDocument.h b/Firestore/Source/Model/FSTDocument.h index add79dc9309..acbec89d528 100644 --- a/Firestore/Source/Model/FSTDocument.h +++ b/Firestore/Source/Model/FSTDocument.h @@ -48,7 +48,7 @@ typedef NS_ENUM(NSInteger, FSTDocumentState) { /** * Whether this document has a local mutation applied that has not yet been acknowledged by Watch. */ -- (BOOL)hasPendingWrites; +- (bool)hasPendingWrites; @end @@ -65,8 +65,8 @@ typedef NS_ENUM(NSInteger, FSTDocumentState) { proto:(GCFSDocument *)proto; - (nullable FSTFieldValue *)fieldForPath:(const firebase::firestore::model::FieldPath &)path; -- (BOOL)hasLocalMutations; -- (BOOL)hasCommittedMutations; +- (bool)hasLocalMutations; +- (bool)hasCommittedMutations; @property(nonatomic, strong, readonly) FSTObjectValue *data; @@ -81,9 +81,9 @@ typedef NS_ENUM(NSInteger, FSTDocumentState) { @interface FSTDeletedDocument : FSTMaybeDocument + (instancetype)documentWithKey:(firebase::firestore::model::DocumentKey)key version:(firebase::firestore::model::SnapshotVersion)version - hasCommittedMutations:(BOOL)committedMutations; + hasCommittedMutations:(bool)committedMutations; -- (BOOL)hasCommittedMutations; +- (bool)hasCommittedMutations; @end diff --git a/Firestore/Source/Model/FSTDocument.mm b/Firestore/Source/Model/FSTDocument.mm index 5158ac7716e..1e35d5bfd7f 100644 --- a/Firestore/Source/Model/FSTDocument.mm +++ b/Firestore/Source/Model/FSTDocument.mm @@ -55,7 +55,7 @@ - (instancetype)initWithKey:(DocumentKey)key version:(SnapshotVersion)version { return self; } -- (BOOL)hasPendingWrites { +- (bool)hasPendingWrites { @throw FSTAbstractMethodException(); // NOLINT } @@ -127,15 +127,15 @@ - (instancetype)initWithData:(FSTObjectValue *)data return self; } -- (BOOL)hasLocalMutations { +- (bool)hasLocalMutations { return _documentState == FSTDocumentStateLocalMutations; } -- (BOOL)hasCommittedMutations { +- (bool)hasCommittedMutations { return _documentState == FSTDocumentStateCommittedMutations; } -- (BOOL)hasPendingWrites { +- (bool)hasPendingWrites { return self.hasLocalMutations || self.hasCommittedMutations; } @@ -174,12 +174,12 @@ - (nullable FSTFieldValue *)fieldForPath:(const FieldPath &)path { @end @implementation FSTDeletedDocument { - BOOL _hasCommittedMutations; + bool _hasCommittedMutations; } + (instancetype)documentWithKey:(DocumentKey)key version:(SnapshotVersion)version - hasCommittedMutations:(BOOL)committedMutations { + hasCommittedMutations:(bool)committedMutations { FSTDeletedDocument *deletedDocument = [[FSTDeletedDocument alloc] initWithKey:std::move(key) version:std::move(version)]; @@ -190,11 +190,11 @@ + (instancetype)documentWithKey:(DocumentKey)key return deletedDocument; } -- (BOOL)hasCommittedMutations { +- (bool)hasCommittedMutations { return _hasCommittedMutations; } -- (BOOL)hasPendingWrites { +- (bool)hasPendingWrites { return self.hasCommittedMutations; } @@ -233,8 +233,8 @@ + (instancetype)documentWithKey:(DocumentKey)key version:(SnapshotVersion)versio return [[FSTUnknownDocument alloc] initWithKey:std::move(key) version:std::move(version)]; } -- (BOOL)hasPendingWrites { - return YES; +- (bool)hasPendingWrites { + return true; } - (BOOL)isEqual:(id)other { diff --git a/Firestore/Source/Model/FSTDocumentSet.h b/Firestore/Source/Model/FSTDocumentSet.h deleted file mode 100644 index 2087a3da59a..00000000000 --- a/Firestore/Source/Model/FSTDocumentSet.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#include "Firestore/core/src/firebase/firestore/model/document_key.h" -#include "Firestore/core/src/firebase/firestore/model/document_map.h" - -@class FSTDocument; - -NS_ASSUME_NONNULL_BEGIN - -/** - * DocumentSet is an immutable (copy-on-write) collection that holds documents in order specified - * by the provided comparator. We always add a document key comparator on top of what is provided - * to guarantee document equality based on the key. - */ -@interface FSTDocumentSet : NSObject - -/** Creates a new, empty FSTDocumentSet sorted by the given comparator, then by keys. */ -+ (instancetype)documentSetWithComparator:(NSComparator)comparator; - -- (instancetype)init __attribute__((unavailable("Use a static constructor instead"))); - -- (NSUInteger)count; - -/** Returns true if the dictionary contains no elements. */ -- (BOOL)isEmpty; - -/** Returns YES if this set contains a document with the given key. */ -- (BOOL)containsKey:(const firebase::firestore::model::DocumentKey &)key; - -/** Returns the document from this set with the given key if it exists or nil if it doesn't. */ -- (FSTDocument *_Nullable)documentForKey:(const firebase::firestore::model::DocumentKey &)key; - -/** - * Returns the first document in the set according to its built in ordering, or nil if the set - * is empty. - */ -- (FSTDocument *_Nullable)firstDocument; - -/** - * Returns the last document in the set according to its built in ordering, or nil if the set - * is empty. - */ -- (FSTDocument *_Nullable)lastDocument; - -/** - * Returns the index of the document with the provided key in the document set. Returns NSNotFound - * if the key is not present. - */ -- (NSUInteger)indexOfKey:(const firebase::firestore::model::DocumentKey &)key; - -- (NSEnumerator *)documentEnumerator; - -/** Returns a copy of the documents in this set as an array. This is O(n) on the size of the set. */ -- (NSArray *)arrayValue; - -/** - * Returns the documents as a `DocumentMap`. This is O(1) as this leverages - * our internal representation. - */ -- (const firebase::firestore::model::DocumentMap &)mapValue; - -/** Returns a new FSTDocumentSet that contains the given document. */ -- (instancetype)documentSetByAddingDocument:(FSTDocument *_Nullable)document; - -/** Returns a new FSTDocumentSet that excludes any document associated with the given key. */ -- (instancetype)documentSetByRemovingKey:(const firebase::firestore::model::DocumentKey &)key; -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Model/FSTDocumentSet.mm b/Firestore/Source/Model/FSTDocumentSet.mm deleted file mode 100644 index c87e2039e54..00000000000 --- a/Firestore/Source/Model/FSTDocumentSet.mm +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#import "Firestore/Source/Model/FSTDocumentSet.h" - -#import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/third_party/Immutable/FSTImmutableSortedSet.h" - -#include "Firestore/core/src/firebase/firestore/model/document_key.h" - -using firebase::firestore::model::DocumentMap; -using firebase::firestore::model::DocumentKey; - -NS_ASSUME_NONNULL_BEGIN - -/** - * The type of the main collection of documents in an FSTDocumentSet. - * @see FSTDocumentSet#sortedSet - */ -typedef FSTImmutableSortedSet SetType; - -@interface FSTDocumentSet () - -- (instancetype)initWithIndex:(DocumentMap &&)index - set:(SetType *)sortedSet NS_DESIGNATED_INITIALIZER; - -/** - * The main collection of documents in the FSTDocumentSet. The documents are ordered by a - * comparator supplied from a query. The SetType collection exists in addition to the index to - * allow ordered traversal of the FSTDocumentSet. - */ -@property(nonatomic, strong, readonly) SetType *sortedSet; -@end - -@implementation FSTDocumentSet { - /** - * An index of the documents in the FSTDocumentSet, indexed by document key. The index - * exists to guarantee the uniqueness of document keys in the set and to allow lookup and removal - * of documents by key. - */ - DocumentMap _index; -} - -+ (instancetype)documentSetWithComparator:(NSComparator)comparator { - SetType *set = [FSTImmutableSortedSet setWithComparator:comparator]; - return [[FSTDocumentSet alloc] initWithIndex:DocumentMap {} set:set]; -} - -- (instancetype)initWithIndex:(DocumentMap &&)index set:(SetType *)sortedSet { - self = [super init]; - if (self) { - _index = std::move(index); - _sortedSet = sortedSet; - } - return self; -} - -- (BOOL)isEqual:(id)other { - if (other == self) { - return YES; - } - if (![other isMemberOfClass:[FSTDocumentSet class]]) { - return NO; - } - - FSTDocumentSet *otherSet = (FSTDocumentSet *)other; - if ([self count] != [otherSet count]) { - return NO; - } - - NSEnumerator *selfIter = [self.sortedSet objectEnumerator]; - NSEnumerator *otherIter = [otherSet.sortedSet objectEnumerator]; - - FSTDocument *selfDoc = [selfIter nextObject]; - FSTDocument *otherDoc = [otherIter nextObject]; - while (selfDoc) { - if (![selfDoc isEqual:otherDoc]) { - return NO; - } - selfDoc = [selfIter nextObject]; - otherDoc = [otherIter nextObject]; - } - return YES; -} - -- (NSUInteger)hash { - NSUInteger hash = 0; - for (FSTDocument *doc in self.sortedSet.objectEnumerator) { - hash = 31 * hash + [doc hash]; - } - return hash; -} - -- (NSString *)description { - return [self.sortedSet description]; -} - -- (NSUInteger)count { - return _index.size(); -} - -- (BOOL)isEmpty { - return _index.empty(); -} - -- (BOOL)containsKey:(const DocumentKey &)key { - return _index.underlying_map().find(key) != _index.underlying_map().end(); -} - -- (FSTDocument *_Nullable)documentForKey:(const DocumentKey &)key { - auto found = _index.underlying_map().find(key); - return found != _index.underlying_map().end() ? static_cast(found->second) : nil; -} - -- (FSTDocument *_Nullable)firstDocument { - return [self.sortedSet firstObject]; -} - -- (FSTDocument *_Nullable)lastDocument { - return [self.sortedSet lastObject]; -} - -- (NSUInteger)indexOfKey:(const DocumentKey &)key { - FSTDocument *doc = [self documentForKey:key]; - return doc ? [self.sortedSet indexOfObject:doc] : NSNotFound; -} - -- (NSEnumerator *)documentEnumerator { - return [self.sortedSet objectEnumerator]; -} - -- (NSArray *)arrayValue { - NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.count]; - for (FSTDocument *doc in self.documentEnumerator) { - [result addObject:doc]; - } - return result; -} - -- (const DocumentMap &)mapValue { - return _index; -} - -- (instancetype)documentSetByAddingDocument:(FSTDocument *_Nullable)document { - // TODO(mcg): look into making document nonnull. - if (!document) { - return self; - } - - // Remove any prior mapping of the document's key before adding, preventing sortedSet from - // accumulating values that aren't in the index. - FSTDocumentSet *removed = [self documentSetByRemovingKey:document.key]; - - DocumentMap index = removed->_index.insert(document.key, document); - SetType *set = [removed.sortedSet setByAddingObject:document]; - return [[FSTDocumentSet alloc] initWithIndex:std::move(index) set:set]; -} - -- (instancetype)documentSetByRemovingKey:(const DocumentKey &)key { - FSTDocument *doc = [self documentForKey:key]; - if (!doc) { - return self; - } - - DocumentMap index = _index.erase(key); - SetType *set = [self.sortedSet setByRemovingObject:doc]; - return [[FSTDocumentSet alloc] initWithIndex:std::move(index) set:set]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h index 5a9b696ce08..4c9f0a48945 100644 --- a/Firestore/Source/Model/FSTFieldValue.h +++ b/Firestore/Source/Model/FSTFieldValue.h @@ -21,6 +21,9 @@ #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/field_mask.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" + +using firebase::firestore::model::FieldValue; @class FSTDocumentKey; @class FIRTimestamp; @@ -85,8 +88,14 @@ enum class ServerTimestampBehavior { None, Estimate, Previous }; */ @interface FSTFieldValue<__covariant T> : NSObject +/** + * Returns the 'type' of this FSTFieldValue. Used for RTTI (rather than isKindOfClass) + * to ease migration to C++. + */ +@property(nonatomic, assign, readonly) FieldValue::Type type; + /** Returns the FSTTypeOrder for this value. */ -- (FSTTypeOrder)typeOrder; +@property(nonatomic, assign, readonly) FSTTypeOrder typeOrder; /** * Converts an FSTFieldValue into the value that users will see in document snapshots. @@ -116,15 +125,6 @@ enum class ServerTimestampBehavior { None, Estimate, Previous }; + (instancetype)nullValue; @end -/** - * A boolean value stored in Firestore. - */ -@interface FSTBooleanValue : FSTFieldValue -+ (instancetype)trueValue; -+ (instancetype)falseValue; -+ (instancetype)booleanValue:(BOOL)value; -@end - /** * Base class inherited from by FSTIntegerValue and FSTDoubleValue. It implements proper number * comparisons between the two types. @@ -149,13 +149,6 @@ enum class ServerTimestampBehavior { None, Estimate, Previous }; - (double)internalValue; @end -/** - * A string stored in Firestore. - */ -@interface FSTStringValue : FSTFieldValue -+ (instancetype)stringValue:(NSString *)value; -@end - /** * A timestamp value stored in Firestore. */ @@ -279,4 +272,12 @@ enum class ServerTimestampBehavior { None, Estimate, Previous }; @end +/** + * A value that delegates to the c++ model::FieldValue. + */ +@interface FSTDelegateValue : FSTFieldValue ++ (instancetype)delegateWithValue:(FieldValue &&)value; +- (const FieldValue &)internalValue; +@end + NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Model/FSTFieldValue.mm b/Firestore/Source/Model/FSTFieldValue.mm index 7730f3987c1..6287c37f13b 100644 --- a/Firestore/Source/Model/FSTFieldValue.mm +++ b/Firestore/Source/Model/FSTFieldValue.mm @@ -16,6 +16,9 @@ #import "Firestore/Source/Model/FSTFieldValue.h" +#include +#include + #import "FIRDocumentSnapshot.h" #import "FIRTimestamp.h" @@ -34,6 +37,7 @@ using firebase::firestore::model::DatabaseId; using firebase::firestore::model::FieldMask; using firebase::firestore::model::FieldPath; +using firebase::firestore::model::FieldValue; using firebase::firestore::util::Comparator; using firebase::firestore::util::CompareMixedNumber; using firebase::firestore::util::DoubleBitwiseEquals; @@ -69,6 +73,13 @@ - (NSComparisonResult)defaultCompare:(FSTFieldValue *)other; @implementation FSTFieldValue +@dynamic type; +@dynamic typeOrder; + +- (FieldValue::Type)type { + @throw FSTAbstractMethodException(); // NOLINT +} + - (FSTTypeOrder)typeOrder { @throw FSTAbstractMethodException(); // NOLINT } @@ -123,6 +134,10 @@ + (instancetype)nullValue { return sharedInstance; } +- (FieldValue::Type)type { + return FieldValue::Type::Null; +} + - (FSTTypeOrder)typeOrder { return FSTTypeOrderNull; } @@ -149,73 +164,6 @@ - (NSComparisonResult)compare:(FSTFieldValue *)other { @end -#pragma mark - FSTBooleanValue - -@interface FSTBooleanValue () -@property(nonatomic, assign, readonly) BOOL internalValue; -@end - -@implementation FSTBooleanValue - -+ (instancetype)trueValue { - static FSTBooleanValue *sharedInstance = nil; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - sharedInstance = [[FSTBooleanValue alloc] initWithValue:YES]; - }); - return sharedInstance; -} - -+ (instancetype)falseValue { - static FSTBooleanValue *sharedInstance = nil; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - sharedInstance = [[FSTBooleanValue alloc] initWithValue:NO]; - }); - return sharedInstance; -} - -+ (instancetype)booleanValue:(BOOL)value { - return value ? [FSTBooleanValue trueValue] : [FSTBooleanValue falseValue]; -} - -- (id)initWithValue:(BOOL)value { - self = [super init]; - if (self) { - _internalValue = value; - } - return self; -} - -- (FSTTypeOrder)typeOrder { - return FSTTypeOrderBoolean; -} - -- (id)value { - return self.internalValue ? @YES : @NO; -} - -- (BOOL)isEqual:(id)other { - // Since we create shared instances for true / false, we can use reference equality. - return self == other; -} - -- (NSUInteger)hash { - return self.internalValue ? 1231 : 1237; -} - -- (NSComparisonResult)compare:(FSTFieldValue *)other { - if ([other isKindOfClass:[FSTBooleanValue class]]) { - return WrapCompare(self.internalValue, ((FSTBooleanValue *)other).internalValue); - } else { - return [self defaultCompare:other]; - } -} - -@end - #pragma mark - FSTNumberValue @implementation FSTNumberValue @@ -225,26 +173,24 @@ - (FSTTypeOrder)typeOrder { } - (NSComparisonResult)compare:(FSTFieldValue *)other { - if (![other isKindOfClass:[FSTNumberValue class]]) { + if (!FieldValue::IsNumber(other.type)) { return [self defaultCompare:other]; } else { - if ([self isKindOfClass:[FSTDoubleValue class]]) { + if (self.type == FieldValue::Type::Double) { double thisDouble = ((FSTDoubleValue *)self).internalValue; - if ([other isKindOfClass:[FSTDoubleValue class]]) { + if (other.type == FieldValue::Type::Double) { return WrapCompare(thisDouble, ((FSTDoubleValue *)other).internalValue); } else { - HARD_ASSERT([other isKindOfClass:[FSTIntegerValue class]], "Unknown number value: %s", - other); + HARD_ASSERT(other.type == FieldValue::Type::Integer, "Unknown number value: %s", other); auto result = CompareMixedNumber(thisDouble, ((FSTIntegerValue *)other).internalValue); return static_cast(result); } } else { int64_t thisInt = ((FSTIntegerValue *)self).internalValue; - if ([other isKindOfClass:[FSTIntegerValue class]]) { + if (other.type == FieldValue::Type::Integer) { return WrapCompare(thisInt, ((FSTIntegerValue *)other).internalValue); } else { - HARD_ASSERT([other isKindOfClass:[FSTDoubleValue class]], "Unknown number value: %s", - other); + HARD_ASSERT(other.type == FieldValue::Type::Double, "Unknown number value: %s", other); double otherDouble = ((FSTDoubleValue *)other).internalValue; auto result = ReverseOrder(CompareMixedNumber(otherDouble, thisInt)); return static_cast(result); @@ -279,10 +225,15 @@ - (id)value { return @(self.internalValue); } +- (FieldValue::Type)type { + return FieldValue::Type::Integer; +} + - (BOOL)isEqual:(id)other { // NOTE: DoubleValue and LongValue instances may compare: the same, but that doesn't make them // equal via isEqual: - return [other isKindOfClass:[FSTIntegerValue class]] && + return [other isKindOfClass:[FSTFieldValue class]] && + ((FSTFieldValue *)other).type == FieldValue::Type::Integer && self.internalValue == ((FSTIntegerValue *)other).internalValue; } @@ -331,13 +282,18 @@ - (id)value { return @(self.internalValue); } +- (FieldValue::Type)type { + return FieldValue::Type::Double; +} + - (BOOL)isEqual:(id)other { // NOTE: DoubleValue and LongValue instances may compare: the same, but that doesn't make them // equal via isEqual: // NOTE: isEqual: should compare NaN equal to itself and -0.0 not equal to 0.0. - return [other isKindOfClass:[FSTDoubleValue class]] && + return [other isKindOfClass:[FSTFieldValue class]] && + ((FSTFieldValue *)other).type == FieldValue::Type::Double && DoubleBitwiseEquals(self.internalValue, ((FSTDoubleValue *)other).internalValue); } @@ -349,65 +305,6 @@ - (NSUInteger)hash { @end -#pragma mark - FSTStringValue - -/** - * Specialization of Comparator for NSStrings. - */ -template <> -struct Comparator { - bool operator()(NSString *left, NSString *right) const { - Comparator lessThan; - return lessThan(MakeString(left), MakeString(right)); - } -}; - -@interface FSTStringValue () -@property(nonatomic, copy, readonly) NSString *internalValue; -@end - -// TODO(b/37267885): Add truncation support -@implementation FSTStringValue - -+ (instancetype)stringValue:(NSString *)value { - return [[FSTStringValue alloc] initWithValue:value]; -} - -- (id)initWithValue:(NSString *)value { - self = [super init]; - if (self) { - _internalValue = [value copy]; - } - return self; -} - -- (FSTTypeOrder)typeOrder { - return FSTTypeOrderString; -} - -- (id)value { - return self.internalValue; -} - -- (BOOL)isEqual:(id)other { - return [other isKindOfClass:[FSTStringValue class]] && - [self.internalValue isEqualToString:((FSTStringValue *)other).internalValue]; -} - -- (NSUInteger)hash { - return self.internalValue ? 1 : 0; -} - -- (NSComparisonResult)compare:(FSTFieldValue *)other { - if ([other isKindOfClass:[FSTStringValue class]]) { - return WrapCompare(self.internalValue, ((FSTStringValue *)other).internalValue); - } else { - return [self defaultCompare:other]; - } -} - -@end - #pragma mark - FSTTimestampValue @interface FSTTimestampValue () @@ -428,6 +325,10 @@ - (id)initWithValue:(FIRTimestamp *)value { return self; } +- (FieldValue::Type)type { + return FieldValue::Type::Timestamp; +} + - (FSTTypeOrder)typeOrder { return FSTTypeOrderTimestamp; } @@ -445,7 +346,8 @@ - (id)valueWithOptions:(FSTFieldValueOptions *)options { } - (BOOL)isEqual:(id)other { - return [other isKindOfClass:[FSTTimestampValue class]] && + return [other isKindOfClass:[FSTFieldValue class]] && + ((FSTFieldValue *)other).type == FieldValue::Type::Timestamp && [self.internalValue isEqual:((FSTTimestampValue *)other).internalValue]; } @@ -454,9 +356,9 @@ - (NSUInteger)hash { } - (NSComparisonResult)compare:(FSTFieldValue *)other { - if ([other isKindOfClass:[FSTTimestampValue class]]) { + if (other.type == FieldValue::Type::Timestamp) { return [self.internalValue compare:((FSTTimestampValue *)other).internalValue]; - } else if ([other isKindOfClass:[FSTServerTimestampValue class]]) { + } else if (other.type == FieldValue::Type::ServerTimestamp) { // Concrete timestamps come before server timestamps. return NSOrderedAscending; } else { @@ -485,6 +387,10 @@ - (id)initWithLocalWriteTime:(FIRTimestamp *)localWriteTime return self; } +- (FieldValue::Type)type { + return FieldValue::Type::ServerTimestamp; +} + - (FSTTypeOrder)typeOrder { return FSTTypeOrderTimestamp; } @@ -507,7 +413,8 @@ - (id)valueWithOptions:(FSTFieldValueOptions *)options { } - (BOOL)isEqual:(id)other { - return [other isKindOfClass:[FSTServerTimestampValue class]] && + return [other isKindOfClass:[FSTFieldValue class]] && + ((FSTFieldValue *)other).type == FieldValue::Type::ServerTimestamp && [self.localWriteTime isEqual:((FSTServerTimestampValue *)other).localWriteTime]; } @@ -520,9 +427,9 @@ - (NSString *)description { } - (NSComparisonResult)compare:(FSTFieldValue *)other { - if ([other isKindOfClass:[FSTServerTimestampValue class]]) { + if (other.type == FieldValue::Type::ServerTimestamp) { return [self.localWriteTime compare:((FSTServerTimestampValue *)other).localWriteTime]; - } else if ([other isKindOfClass:[FSTTimestampValue class]]) { + } else if (other.type == FieldValue::Type::Timestamp) { // Server timestamps come after all concrete timestamps. return NSOrderedDescending; } else { @@ -552,6 +459,10 @@ - (id)initWithValue:(FIRGeoPoint *)value { return self; } +- (FieldValue::Type)type { + return FieldValue::Type::GeoPoint; +} + - (FSTTypeOrder)typeOrder { return FSTTypeOrderGeoPoint; } @@ -561,7 +472,8 @@ - (id)value { } - (BOOL)isEqual:(id)other { - return [other isKindOfClass:[FSTGeoPointValue class]] && + return [other isKindOfClass:[FSTFieldValue class]] && + ((FSTFieldValue *)other).type == FieldValue::Type::GeoPoint && [self.internalValue isEqual:((FSTGeoPointValue *)other).internalValue]; } @@ -570,7 +482,7 @@ - (NSUInteger)hash { } - (NSComparisonResult)compare:(FSTFieldValue *)other { - if ([other isKindOfClass:[FSTGeoPointValue class]]) { + if (other.type == FieldValue::Type::GeoPoint) { return [self.internalValue compare:((FSTGeoPointValue *)other).internalValue]; } else { return [self defaultCompare:other]; @@ -615,6 +527,10 @@ - (id)initWithValue:(NSData *)value { return self; } +- (FieldValue::Type)type { + return FieldValue::Type::Blob; +} + - (FSTTypeOrder)typeOrder { return FSTTypeOrderBlob; } @@ -624,7 +540,8 @@ - (id)value { } - (BOOL)isEqual:(id)other { - return [other isKindOfClass:[FSTBlobValue class]] && + return [other isKindOfClass:[FSTFieldValue class]] && + ((FSTFieldValue *)other).type == FieldValue::Type::Blob && [self.internalValue isEqual:((FSTBlobValue *)other).internalValue]; } @@ -633,7 +550,7 @@ - (NSUInteger)hash { } - (NSComparisonResult)compare:(FSTFieldValue *)other { - if ([other isKindOfClass:[FSTBlobValue class]]) { + if (other.type == FieldValue::Type::Blob) { return CompareBytes(self.internalValue, ((FSTBlobValue *)other).internalValue); } else { return [self defaultCompare:other]; @@ -667,6 +584,10 @@ - (id)value { return self.key; } +- (FieldValue::Type)type { + return FieldValue::Type::Reference; +} + - (FSTTypeOrder)typeOrder { return FSTTypeOrderReference; } @@ -675,7 +596,8 @@ - (BOOL)isEqual:(id)other { if (other == self) { return YES; } - if (![other isKindOfClass:[FSTReferenceValue class]]) { + if (!([other isKindOfClass:[FSTFieldValue class]] && + ((FSTFieldValue *)other).type == FieldValue::Type::Reference)) { return NO; } @@ -690,7 +612,7 @@ - (NSUInteger)hash { } - (NSComparisonResult)compare:(FSTFieldValue *)other { - if ([other isKindOfClass:[FSTReferenceValue class]]) { + if (other.type == FieldValue::Type::Reference) { FSTReferenceValue *ref = (FSTReferenceValue *)other; NSComparisonResult cmp = WrapCompare(self.databaseID->project_id(), ref.databaseID->project_id()); @@ -708,6 +630,18 @@ - (NSComparisonResult)compare:(FSTFieldValue *)other { #pragma mark - FSTObjectValue +/** + * Specialization of Comparator for NSStrings. + */ +// TODO(b/37267885): Add truncation support +template <> +struct Comparator { + bool operator()(NSString *left, NSString *right) const { + Comparator lessThan; + return lessThan(MakeString(left), MakeString(right)); + } +}; + static const NSComparator StringComparator = ^NSComparisonResult(NSString *left, NSString *right) { return WrapCompare(left, right); }; @@ -764,6 +698,10 @@ - (id)valueWithOptions:(FSTFieldValueOptions *)options { return result; } +- (FieldValue::Type)type { + return FieldValue::Type::Object; +} + - (FSTTypeOrder)typeOrder { return FSTTypeOrderObject; } @@ -772,7 +710,8 @@ - (BOOL)isEqual:(id)other { if (other == self) { return YES; } - if (![other isKindOfClass:[FSTObjectValue class]]) { + if (!([other isKindOfClass:[FSTFieldValue class]] && + ((FSTFieldValue *)other).type == FieldValue::Type::Object)) { return NO; } @@ -785,7 +724,7 @@ - (NSUInteger)hash { } - (NSComparisonResult)compare:(FSTFieldValue *)other { - if ([other isKindOfClass:[FSTObjectValue class]]) { + if (other.type == FieldValue::Type::Object) { FSTImmutableSortedDictionary *selfDict = self.internalValue; FSTImmutableSortedDictionary *otherDict = ((FSTObjectValue *)other).internalValue; NSEnumerator *enumerator1 = [selfDict keyEnumerator]; @@ -838,7 +777,7 @@ - (FSTObjectValue *)objectBySettingValue:(FSTFieldValue *)value // around the result. FSTFieldValue *child = [_internalValue objectForKey:childName]; FSTObjectValue *childObject; - if ([child isKindOfClass:[FSTObjectValue class]]) { + if (child.type == FieldValue::Type::Object) { childObject = (FSTObjectValue *)child; } else { // If the child is not found or is a primitive type, pretend as if an empty object lived @@ -858,7 +797,7 @@ - (FSTObjectValue *)objectByDeletingPath:(const FieldPath &)fieldPath { initWithImmutableDictionary:[_internalValue dictionaryByRemovingObjectForKey:childName]]; } else { FSTFieldValue *child = _internalValue[childName]; - if ([child isKindOfClass:[FSTObjectValue class]]) { + if (child.type == FieldValue::Type::Object) { FSTObjectValue *newChild = [((FSTObjectValue *)child) objectByDeletingPath:fieldPath.PopFirst()]; return [self objectBySettingValue:newChild forField:childName]; @@ -941,12 +880,16 @@ - (id)valueWithOptions:(FSTFieldValueOptions *)options { return result; } +- (FieldValue::Type)type { + return FieldValue::Type::Array; +} + - (FSTTypeOrder)typeOrder { return FSTTypeOrderArray; } - (NSComparisonResult)compare:(FSTFieldValue *)other { - if ([other isKindOfClass:[FSTArrayValue class]]) { + if (other.type == FieldValue::Type::Array) { NSArray *selfArray = self.internalValue; NSArray *otherArray = ((FSTArrayValue *)other).internalValue; NSUInteger minLength = MIN(selfArray.count, otherArray.count); @@ -964,4 +907,123 @@ - (NSComparisonResult)compare:(FSTFieldValue *)other { @end +@implementation FSTDelegateValue { + FieldValue _internalValue; +} + ++ (instancetype)delegateWithValue:(FieldValue &&)value { + return [[FSTDelegateValue alloc] initWithValue:std::move(value)]; +} + +- (const FieldValue &)internalValue { + return _internalValue; +} + +- (id)initWithValue:(FieldValue &&)value { + self = [super init]; + if (self) { + _internalValue = std::move(value); + } + return self; +} + +- (FieldValue::Type)type { + return self.internalValue.type(); +} + +- (FSTTypeOrder)typeOrder { + switch (self.internalValue.type()) { + case FieldValue::Type::Null: + return FSTTypeOrderNull; + case FieldValue::Type::Boolean: + return FSTTypeOrderBoolean; + case FieldValue::Type::Integer: + case FieldValue::Type::Double: + return FSTTypeOrderNumber; + case FieldValue::Type::Timestamp: + case FieldValue::Type::ServerTimestamp: + return FSTTypeOrderTimestamp; + case FieldValue::Type::String: + return FSTTypeOrderString; + case FieldValue::Type::Blob: + return FSTTypeOrderBlob; + case FieldValue::Type::Reference: + return FSTTypeOrderReference; + case FieldValue::Type::GeoPoint: + return FSTTypeOrderGeoPoint; + case FieldValue::Type::Array: + return FSTTypeOrderArray; + case FieldValue::Type::Object: + return FSTTypeOrderObject; + } + UNREACHABLE(); +} + +- (BOOL)isEqual:(id)other { + // TODO(rsgowman): Port the other FST*Value's, and then remove this comment: + // + // Simplification: We'll assume that (eg) FSTBooleanValue(true) != + // FSTDelegateValue(FieldValue::FromBoolean(true)). That's not great. We'll + // handle this by ensuring that we remove (eg) FSTBooleanValue at the same + // time that FSTDelegateValue handles (eg) booleans to ensure this case never + // occurs. + + if (other == self) { + return YES; + } + if (![other isKindOfClass:[self class]]) { + return NO; + } + + return self.internalValue == ((FSTDelegateValue *)other).internalValue; +} + +- (id)value { + switch (self.internalValue.type()) { + case FieldValue::Type::Null: + HARD_FAIL("TODO(rsgowman): implement"); + case FieldValue::Type::Boolean: + return self.internalValue.boolean_value() ? @YES : @NO; + case FieldValue::Type::Integer: + case FieldValue::Type::Double: + case FieldValue::Type::Timestamp: + case FieldValue::Type::ServerTimestamp: + HARD_FAIL("TODO(rsgowman): implement"); + case FieldValue::Type::String: + return util::WrapNSString(self.internalValue.string_value()); + case FieldValue::Type::Blob: + case FieldValue::Type::Reference: + case FieldValue::Type::GeoPoint: + case FieldValue::Type::Array: + case FieldValue::Type::Object: + HARD_FAIL("TODO(rsgowman): implement"); + } + UNREACHABLE(); +} + +- (NSComparisonResult)compare:(FSTFieldValue *)other { + // TODO(rsgowman): Port the other FST*Value's, and then remove this comment: + // + // Simplification: We'll assume that if Comparable(self.type, other.type), + // then other must be a FSTDelegateValue. That's not great. We'll handle this + // by ensuring that we remove (eg) FSTBooleanValue at the same time that + // FSTDelegateValue handles (eg) booleans to ensure this case never occurs. + + if (FieldValue::Comparable(self.type, other.type)) { + HARD_ASSERT([other isKindOfClass:[FSTDelegateValue class]]); + return WrapCompare(self.internalValue, ((FSTDelegateValue *)other).internalValue); + } else { + return [self defaultCompare:other]; + } +} + +- (NSUInteger)hash { + return self.internalValue.Hash(); +} + +@end + +template <> +struct Comparator : public std::less {}; + NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Public/FIRCollectionReference.h b/Firestore/Source/Public/FIRCollectionReference.h index bc9a56af932..39be000578e 100644 --- a/Firestore/Source/Public/FIRCollectionReference.h +++ b/Firestore/Source/Public/FIRCollectionReference.h @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(CollectionReference) @interface FIRCollectionReference : FIRQuery -/** */ +/** :nodoc: */ - (id)init __attribute__((unavailable("FIRCollectionReference cannot be created directly."))); /** ID of the referenced collection. */ diff --git a/Firestore/Source/Public/FIRDocumentChange.h b/Firestore/Source/Public/FIRDocumentChange.h index 47170673068..3e4a0120d28 100644 --- a/Firestore/Source/Public/FIRDocumentChange.h +++ b/Firestore/Source/Public/FIRDocumentChange.h @@ -40,7 +40,7 @@ typedef NS_ENUM(NSInteger, FIRDocumentChangeType) { NS_SWIFT_NAME(DocumentChange) @interface FIRDocumentChange : NSObject -/** */ +/** :nodoc: */ - (id)init __attribute__((unavailable("FIRDocumentChange cannot be created directly."))); /** The type of change that occurred (added, modified, or removed). */ diff --git a/Firestore/Source/Public/FIRDocumentReference.h b/Firestore/Source/Public/FIRDocumentReference.h index 216a8dc93f4..c110bcd10e7 100644 --- a/Firestore/Source/Public/FIRDocumentReference.h +++ b/Firestore/Source/Public/FIRDocumentReference.h @@ -37,7 +37,7 @@ typedef void (^FIRDocumentSnapshotBlock)(FIRDocumentSnapshot *_Nullable snapshot NS_SWIFT_NAME(DocumentReference) @interface FIRDocumentReference : NSObject -/** */ +/** :nodoc: */ - (instancetype)init __attribute__((unavailable("FIRDocumentReference cannot be created directly."))); diff --git a/Firestore/Source/Public/FIRDocumentSnapshot.h b/Firestore/Source/Public/FIRDocumentSnapshot.h index 669fe07dca6..9a3f61b19a2 100644 --- a/Firestore/Source/Public/FIRDocumentSnapshot.h +++ b/Firestore/Source/Public/FIRDocumentSnapshot.h @@ -58,7 +58,7 @@ typedef NS_ENUM(NSInteger, FIRServerTimestampBehavior) { NS_SWIFT_NAME(DocumentSnapshot) @interface FIRDocumentSnapshot : NSObject -/** */ +/** :nodoc: */ - (instancetype)init __attribute__((unavailable("FIRDocumentSnapshot cannot be created directly."))); @@ -151,7 +151,7 @@ NS_SWIFT_NAME(DocumentSnapshot) NS_SWIFT_NAME(QueryDocumentSnapshot) @interface FIRQueryDocumentSnapshot : FIRDocumentSnapshot -/** */ +/** :nodoc: */ - (instancetype)init __attribute__((unavailable("FIRQueryDocumentSnapshot cannot be created directly."))); diff --git a/Firestore/Source/Public/FIRFieldValue.h b/Firestore/Source/Public/FIRFieldValue.h index 7973157515b..24d1007a5e0 100644 --- a/Firestore/Source/Public/FIRFieldValue.h +++ b/Firestore/Source/Public/FIRFieldValue.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(FieldValue) @interface FIRFieldValue : NSObject -/** */ +/** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** Used with updateData() to mark a field for deletion. */ diff --git a/Firestore/Source/Public/FIRFirestore.h b/Firestore/Source/Public/FIRFirestore.h index f2c914398ee..5c6e6b63b7c 100644 --- a/Firestore/Source/Public/FIRFirestore.h +++ b/Firestore/Source/Public/FIRFirestore.h @@ -20,6 +20,7 @@ @class FIRCollectionReference; @class FIRDocumentReference; @class FIRFirestoreSettings; +@class FIRQuery; @class FIRTransaction; @class FIRWriteBatch; @@ -33,7 +34,7 @@ NS_SWIFT_NAME(Firestore) @interface FIRFirestore : NSObject #pragma mark - Initializing -/** */ +/** :nodoc: */ - (instancetype)init __attribute__((unavailable("Use a static constructor method."))); /** @@ -91,6 +92,18 @@ NS_SWIFT_NAME(Firestore) */ - (FIRDocumentReference *)documentWithPath:(NSString *)documentPath NS_SWIFT_NAME(document(_:)); +#pragma mark - Collection Group Queries + +/** + * Creates and returns a new `Query` that includes all documents in the database that are contained + * in a collection or subcollection with the given collectionID. + * + * @param collectionID Identifies the collections to query over. Every collection or subcollection + * with this ID as the last segment of its path will be included. Cannot contain a slash. + * @return The created `Query`. + */ +- (FIRQuery *)collectionGroupWithID:(NSString *)collectionID NS_SWIFT_NAME(collectionGroup(_:)); + #pragma mark - Transactions and Write Batches /** @@ -136,9 +149,7 @@ NS_SWIFT_NAME(Firestore) #pragma mark - Logging /** Enables or disables logging from the Firestore client. */ -+ (void)enableLogging:(BOOL)logging - DEPRECATED_MSG_ATTRIBUTE("Use FirebaseConfiguration.shared.setLoggerLevel(.debug) to enable " - "logging."); ++ (void)enableLogging:(BOOL)logging; #pragma mark - Network diff --git a/Firestore/Source/Public/FIRGeoPoint.h b/Firestore/Source/Public/FIRGeoPoint.h index ee7a7ea3d05..290b2b45ec2 100644 --- a/Firestore/Source/Public/FIRGeoPoint.h +++ b/Firestore/Source/Public/FIRGeoPoint.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(GeoPoint) @interface FIRGeoPoint : NSObject -/** */ +/** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** diff --git a/Firestore/Source/Public/FIRQuery.h b/Firestore/Source/Public/FIRQuery.h index 2b02a3c4270..436c185b4a2 100644 --- a/Firestore/Source/Public/FIRQuery.h +++ b/Firestore/Source/Public/FIRQuery.h @@ -35,7 +35,7 @@ typedef void (^FIRQuerySnapshotBlock)(FIRQuerySnapshot *_Nullable snapshot, */ NS_SWIFT_NAME(Query) @interface FIRQuery : NSObject -/** */ +/** :nodoc: */ - (id)init __attribute__((unavailable("FIRQuery cannot be created directly."))); /** The `FIRFirestore` for the Firestore database (useful for performing transactions, etc.). */ diff --git a/Firestore/Source/Public/FIRQuerySnapshot.h b/Firestore/Source/Public/FIRQuerySnapshot.h index 6a7e60ddcef..268598fc0b1 100644 --- a/Firestore/Source/Public/FIRQuerySnapshot.h +++ b/Firestore/Source/Public/FIRQuerySnapshot.h @@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(QuerySnapshot) @interface FIRQuerySnapshot : NSObject -/** */ +/** :nodoc: */ - (id)init __attribute__((unavailable("FIRQuerySnapshot cannot be created directly."))); /** diff --git a/Firestore/Source/Public/FIRSnapshotMetadata.h b/Firestore/Source/Public/FIRSnapshotMetadata.h index f47f3839810..043d8198acb 100644 --- a/Firestore/Source/Public/FIRSnapshotMetadata.h +++ b/Firestore/Source/Public/FIRSnapshotMetadata.h @@ -22,6 +22,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(SnapshotMetadata) @interface FIRSnapshotMetadata : NSObject +/** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** diff --git a/Firestore/Source/Public/FIRTimestamp.h b/Firestore/Source/Public/FIRTimestamp.h index bf4aff47e05..cea316b9887 100644 --- a/Firestore/Source/Public/FIRTimestamp.h +++ b/Firestore/Source/Public/FIRTimestamp.h @@ -33,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(Timestamp) @interface FIRTimestamp : NSObject -/** */ +/** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** diff --git a/Firestore/Source/Public/FIRTransaction.h b/Firestore/Source/Public/FIRTransaction.h index e53414d37af..ede0fb9d422 100644 --- a/Firestore/Source/Public/FIRTransaction.h +++ b/Firestore/Source/Public/FIRTransaction.h @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(Transaction) @interface FIRTransaction : NSObject -/** */ +/** :nodoc: */ - (id)init __attribute__((unavailable("FIRTransaction cannot be created directly."))); /** diff --git a/Firestore/Source/Remote/FSTSerializerBeta.mm b/Firestore/Source/Remote/FSTSerializerBeta.mm index 3e8fc6809da..69b7c7f3665 100644 --- a/Firestore/Source/Remote/FSTSerializerBeta.mm +++ b/Firestore/Source/Remote/FSTSerializerBeta.mm @@ -41,6 +41,7 @@ #import "Firestore/Source/Model/FSTMutationBatch.h" #include "Firestore/core/include/firebase/firestore/firestore_errors.h" +#include "Firestore/core/src/firebase/firestore/core/filter.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_mask.h" @@ -60,6 +61,7 @@ namespace util = firebase::firestore::util; using firebase::Timestamp; using firebase::firestore::FirestoreErrorCode; +using firebase::firestore::core::Filter; using firebase::firestore::model::ArrayTransform; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::DocumentKey; @@ -199,49 +201,45 @@ - (NSString *)encodedDatabaseID { #pragma mark - FSTFieldValue <=> Value proto - (GCFSValue *)encodedFieldValue:(FSTFieldValue *)fieldValue { - Class fieldClass = [fieldValue class]; - if (fieldClass == [FSTNullValue class]) { - return [self encodedNull]; - - } else if (fieldClass == [FSTBooleanValue class]) { - return [self encodedBool:[[fieldValue value] boolValue]]; - - } else if (fieldClass == [FSTIntegerValue class]) { - return [self encodedInteger:[[fieldValue value] longLongValue]]; - - } else if (fieldClass == [FSTDoubleValue class]) { - return [self encodedDouble:[[fieldValue value] doubleValue]]; - - } else if (fieldClass == [FSTStringValue class]) { - return [self encodedString:[fieldValue value]]; - - } else if (fieldClass == [FSTTimestampValue class]) { - FIRTimestamp *value = static_cast([fieldValue value]); - return [self encodedTimestampValue:Timestamp{value.seconds, value.nanoseconds}]; - } else if (fieldClass == [FSTGeoPointValue class]) { - return [self encodedGeoPointValue:[fieldValue value]]; - - } else if (fieldClass == [FSTBlobValue class]) { - return [self encodedBlobValue:[fieldValue value]]; - - } else if (fieldClass == [FSTReferenceValue class]) { - FSTReferenceValue *ref = (FSTReferenceValue *)fieldValue; - DocumentKey key = [[ref value] key]; - return [self encodedReferenceValueForDatabaseID:[ref databaseID] key:key]; - - } else if (fieldClass == [FSTObjectValue class]) { - GCFSValue *result = [GCFSValue message]; - result.mapValue = [self encodedMapValue:(FSTObjectValue *)fieldValue]; - return result; - - } else if (fieldClass == [FSTArrayValue class]) { - GCFSValue *result = [GCFSValue message]; - result.arrayValue = [self encodedArrayValue:(FSTArrayValue *)fieldValue]; - return result; + switch (fieldValue.type) { + case FieldValue::Type::Null: + return [self encodedNull]; + case FieldValue::Type::Boolean: + return [self encodedBool:[[fieldValue value] boolValue]]; + case FieldValue::Type::Integer: + return [self encodedInteger:[[fieldValue value] longLongValue]]; + case FieldValue::Type::Double: + return [self encodedDouble:[[fieldValue value] doubleValue]]; + case FieldValue::Type::Timestamp: { + FIRTimestamp *value = static_cast([fieldValue value]); + return [self encodedTimestampValue:Timestamp{value.seconds, value.nanoseconds}]; + } + case FieldValue::Type::String: + return [self encodedString:[fieldValue value]]; + case FieldValue::Type::Blob: + return [self encodedBlobValue:[fieldValue value]]; + case FieldValue::Type::Reference: { + FSTReferenceValue *ref = (FSTReferenceValue *)fieldValue; + DocumentKey key = [[ref value] key]; + return [self encodedReferenceValueForDatabaseID:[ref databaseID] key:key]; + } + case FieldValue::Type::GeoPoint: + return [self encodedGeoPointValue:[fieldValue value]]; + case FieldValue::Type::Array: { + GCFSValue *result = [GCFSValue message]; + result.arrayValue = [self encodedArrayValue:(FSTArrayValue *)fieldValue]; + return result; + } + case FieldValue::Type::Object: { + GCFSValue *result = [GCFSValue message]; + result.mapValue = [self encodedMapValue:(FSTObjectValue *)fieldValue]; + return result; + } - } else { - HARD_FAIL("Unhandled type %s on %s", NSStringFromClass([fieldValue class]), fieldValue); + case FieldValue::Type::ServerTimestamp: + HARD_FAIL("Unhandled type %s on %s", NSStringFromClass([fieldValue class]), fieldValue); } + UNREACHABLE(); } - (FSTFieldValue *)decodedFieldValue:(GCFSValue *)valueProto { @@ -250,7 +248,7 @@ - (FSTFieldValue *)decodedFieldValue:(GCFSValue *)valueProto { return [FSTNullValue nullValue]; case GCFSValue_ValueType_OneOfCase_BooleanValue: - return [FSTBooleanValue booleanValue:valueProto.booleanValue]; + return FieldValue::FromBoolean(valueProto.booleanValue).Wrap(); case GCFSValue_ValueType_OneOfCase_IntegerValue: return [FSTIntegerValue integerValue:valueProto.integerValue]; @@ -259,7 +257,7 @@ - (FSTFieldValue *)decodedFieldValue:(GCFSValue *)valueProto { return [FSTDoubleValue doubleValue:valueProto.doubleValue]; case GCFSValue_ValueType_OneOfCase_StringValue: - return [FSTStringValue stringValue:valueProto.stringValue]; + return FieldValue::FromString(util::MakeString(valueProto.stringValue)).Wrap(); case GCFSValue_ValueType_OneOfCase_TimestampValue: { Timestamp value = [self decodedTimestamp:valueProto.timestampValue]; @@ -777,10 +775,16 @@ - (FSTQuery *)decodedQueryFromDocumentsTarget:(GCFSTarget_DocumentsTarget *)targ - (GCFSTarget_QueryTarget *)encodedQueryTarget:(FSTQuery *)query { // Dissect the path into parent, collectionId, and optional key filter. GCFSTarget_QueryTarget *queryTarget = [GCFSTarget_QueryTarget message]; - if (query.path.size() == 0) { - queryTarget.parent = [self encodedQueryPath:query.path]; + const ResourcePath &path = query.path; + if (query.collectionGroup) { + HARD_ASSERT(path.size() % 2 == 0, + "Collection group queries should be within a document path or root."); + queryTarget.parent = [self encodedQueryPath:path]; + GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message]; + from.collectionId = query.collectionGroup; + from.allDescendants = YES; + [queryTarget.structuredQuery.fromArray addObject:from]; } else { - const ResourcePath &path = query.path; HARD_ASSERT(path.size() % 2 != 0, "Document queries with filters are not supported."); queryTarget.parent = [self encodedQueryPath:path.PopLast()]; GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message]; @@ -818,13 +822,18 @@ - (FSTQuery *)decodedQueryFromQueryTarget:(GCFSTarget_QueryTarget *)target { ResourcePath path = [self decodedQueryPath:target.parent]; GCFSStructuredQuery *query = target.structuredQuery; + NSString *collectionGroup; NSUInteger fromCount = query.fromArray_Count; if (fromCount > 0) { HARD_ASSERT(fromCount == 1, "StructuredQuery.from with more than one collection is not supported."); GCFSStructuredQuery_CollectionSelector *from = query.fromArray[0]; - path = path.Append(util::MakeString(from.collectionId)); + if (from.allDescendants) { + collectionGroup = from.collectionId; + } else { + path = path.Append(util::MakeString(from.collectionId)); + } } NSArray *filterBy; @@ -857,6 +866,7 @@ - (FSTQuery *)decodedQueryFromQueryTarget:(GCFSTarget_QueryTarget *)target { } return [[FSTQuery alloc] initWithPath:path + collectionGroup:collectionGroup filterBy:filterBy orderBy:orderBy limit:limit @@ -933,7 +943,7 @@ - (GCFSStructuredQuery_Filter *)encodedRelationFilter:(FSTRelationFilter *)filte - (FSTRelationFilter *)decodedRelationFilter:(GCFSStructuredQuery_FieldFilter *)proto { FieldPath fieldPath = FieldPath::FromServerFormat(util::MakeString(proto.field.fieldPath)); - FSTRelationFilterOperator filterOperator = [self decodedRelationFilterOperator:proto.op]; + Filter::Operator filterOperator = [self decodedRelationFilterOperator:proto.op]; FSTFieldValue *value = [self decodedFieldValue:proto.value]; return [FSTRelationFilter filterWithField:fieldPath filterOperator:filterOperator value:value]; } @@ -972,40 +982,40 @@ - (GCFSStructuredQuery_FieldReference *)encodedFieldPath:(const FieldPath &)fiel } - (GCFSStructuredQuery_FieldFilter_Operator)encodedRelationFilterOperator: - (FSTRelationFilterOperator)filterOperator { + (Filter::Operator)filterOperator { switch (filterOperator) { - case FSTRelationFilterOperatorLessThan: + case Filter::Operator::LessThan: return GCFSStructuredQuery_FieldFilter_Operator_LessThan; - case FSTRelationFilterOperatorLessThanOrEqual: + case Filter::Operator::LessThanOrEqual: return GCFSStructuredQuery_FieldFilter_Operator_LessThanOrEqual; - case FSTRelationFilterOperatorEqual: + case Filter::Operator::Equal: return GCFSStructuredQuery_FieldFilter_Operator_Equal; - case FSTRelationFilterOperatorGreaterThanOrEqual: + case Filter::Operator::GreaterThanOrEqual: return GCFSStructuredQuery_FieldFilter_Operator_GreaterThanOrEqual; - case FSTRelationFilterOperatorGreaterThan: + case Filter::Operator::GreaterThan: return GCFSStructuredQuery_FieldFilter_Operator_GreaterThan; - case FSTRelationFilterOperatorArrayContains: + case Filter::Operator::ArrayContains: return GCFSStructuredQuery_FieldFilter_Operator_ArrayContains; default: - HARD_FAIL("Unhandled FSTRelationFilterOperator: %s", filterOperator); + HARD_FAIL("Unhandled Filter::Operator: %s", filterOperator); } } -- (FSTRelationFilterOperator)decodedRelationFilterOperator: +- (Filter::Operator)decodedRelationFilterOperator: (GCFSStructuredQuery_FieldFilter_Operator)filterOperator { switch (filterOperator) { case GCFSStructuredQuery_FieldFilter_Operator_LessThan: - return FSTRelationFilterOperatorLessThan; + return Filter::Operator::LessThan; case GCFSStructuredQuery_FieldFilter_Operator_LessThanOrEqual: - return FSTRelationFilterOperatorLessThanOrEqual; + return Filter::Operator::LessThanOrEqual; case GCFSStructuredQuery_FieldFilter_Operator_Equal: - return FSTRelationFilterOperatorEqual; + return Filter::Operator::Equal; case GCFSStructuredQuery_FieldFilter_Operator_GreaterThanOrEqual: - return FSTRelationFilterOperatorGreaterThanOrEqual; + return Filter::Operator::GreaterThanOrEqual; case GCFSStructuredQuery_FieldFilter_Operator_GreaterThan: - return FSTRelationFilterOperatorGreaterThan; + return Filter::Operator::GreaterThan; case GCFSStructuredQuery_FieldFilter_Operator_ArrayContains: - return FSTRelationFilterOperatorArrayContains; + return Filter::Operator::ArrayContains; default: HARD_FAIL("Unhandled FieldFilter.operator: %s", filterOperator); } diff --git a/Firestore/Source/Util/FSTAsyncQueryListener.h b/Firestore/Source/Util/FSTAsyncQueryListener.h index 82a3712df8a..8588fbe64a6 100644 --- a/Firestore/Source/Util/FSTAsyncQueryListener.h +++ b/Firestore/Source/Util/FSTAsyncQueryListener.h @@ -21,10 +21,8 @@ NS_ASSUME_NONNULL_BEGIN -@class FSTQueryListener; - /** - * A wrapper class around FSTQueryListener that dispatches events asynchronously. + * A wrapper class around QueryListener that dispatches events asynchronously. */ @interface FSTAsyncQueryListener : NSObject diff --git a/Firestore/Source/Util/FSTAsyncQueryListener.mm b/Firestore/Source/Util/FSTAsyncQueryListener.mm deleted file mode 100644 index 8133feeac46..00000000000 --- a/Firestore/Source/Util/FSTAsyncQueryListener.mm +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "Firestore/Source/Util/FSTAsyncQueryListener.h" - -#include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" -#include "Firestore/core/src/firebase/firestore/util/statusor.h" - -using firebase::firestore::core::ViewSnapshot; -using firebase::firestore::core::ViewSnapshotHandler; -using firebase::firestore::util::Executor; -using firebase::firestore::util::StatusOr; - -@implementation FSTAsyncQueryListener { - volatile BOOL _muted; - ViewSnapshotHandler _snapshotHandler; - Executor *_executor; -} - -- (instancetype)initWithExecutor:(Executor *)executor - snapshotHandler:(ViewSnapshotHandler &&)snapshotHandler { - if (self = [super init]) { - _executor = executor; - _snapshotHandler = snapshotHandler; - } - return self; -} - -- (ViewSnapshotHandler)asyncSnapshotHandler { - // Retain `self` strongly in resulting snapshot handler so that even if the - // user releases the `FSTAsyncQueryListener` we'll continue to deliver - // events. This is done specifically to facilitate the common case where - // users just want to turn on notifications "forever" and don't want to have - // to keep track of our handle to keep them going. - return [self](const StatusOr &maybe_snapshot) { - // TODO(c++14): move into lambda. - self->_executor->Execute([self, maybe_snapshot] { - if (!self->_muted) { - self->_snapshotHandler(maybe_snapshot); - } - }); - }; -} - -- (void)mute { - _muted = true; -} - -@end diff --git a/Firestore/Source/Util/FSTUsageValidation.h b/Firestore/Source/Util/FSTUsageValidation.h deleted file mode 100644 index afc54128c82..00000000000 --- a/Firestore/Source/Util/FSTUsageValidation.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** Helper for creating a general exception for invalid usage of an API. */ -NSException *FSTInvalidUsage(NSString *exceptionName, NSString *format, ...); - -/** - * Macro to throw exceptions in response to API usage errors. Avoids the lint warning you usually - * get when using @throw and (unlike a function) doesn't trigger warnings about not all codepaths - * returning a value. - * - * Exceptions should only be used for programmer errors made by consumers of the SDK, e.g. - * invalid method arguments. - * - * For recoverable runtime errors, use NSError**. - * For internal programming errors, use HARD_FAIL(). - */ -#define FSTThrowInvalidUsage(exceptionName, format, ...) \ - do { \ - @throw FSTInvalidUsage(exceptionName, format, ##__VA_ARGS__); \ - } while (0) - -#define FSTThrowInvalidArgument(format, ...) \ - do { \ - @throw FSTInvalidUsage(@"FIRInvalidArgumentException", format, ##__VA_ARGS__); \ - } while (0) - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Swift/Tests/API/BasicCompileTests.swift b/Firestore/Swift/Tests/API/BasicCompileTests.swift index 0889979c94e..8308dea96b7 100644 --- a/Firestore/Swift/Tests/API/BasicCompileTests.swift +++ b/Firestore/Swift/Tests/API/BasicCompileTests.swift @@ -19,7 +19,7 @@ import Foundation import XCTest -import FirebaseFirestore +import Firebase class BasicCompileTests: XCTestCase { func testCompiled() { @@ -90,7 +90,7 @@ func makeRefs(database db: Firestore) -> (CollectionReference, DocumentReference } func makeQuery(collection collectionRef: CollectionReference) -> Query { - let query = collectionRef.whereField(FieldPath(["name"]), isEqualTo: "Fred") + var query = collectionRef.whereField(FieldPath(["name"]), isEqualTo: "Fred") .whereField("age", isGreaterThanOrEqualTo: 24) .whereField("tags", arrayContains: "active") .whereField(FieldPath(["tags"]), arrayContains: "active") @@ -99,6 +99,8 @@ func makeQuery(collection collectionRef: CollectionReference) -> Query { .order(by: "name", descending: true) .limit(to: 10) + query = collectionRef.firestore.collectionGroup("collection") + return query } @@ -410,7 +412,11 @@ func types() { let _: Firestore let _: FirestoreSettings let _: GeoPoint + let _: Firebase.GeoPoint + let _: FirebaseFirestore.GeoPoint let _: Timestamp + let _: Firebase.Timestamp + let _: FirebaseFirestore.Timestamp let _: ListenerRegistration let _: Query let _: QuerySnapshot diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt index a7fd72a8508..b9812921311 100644 --- a/Firestore/core/CMakeLists.txt +++ b/Firestore/core/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory(include/firebase/firestore) add_subdirectory(src/firebase/firestore) +add_subdirectory(src/firebase/firestore/api) add_subdirectory(src/firebase/firestore/auth) add_subdirectory(src/firebase/firestore/core) add_subdirectory(src/firebase/firestore/immutable) diff --git a/Firestore/core/include/firebase/firestore/timestamp.h b/Firestore/core/include/firebase/firestore/timestamp.h index 1736981ed34..fe4ac8f318f 100644 --- a/Firestore/core/include/firebase/firestore/timestamp.h +++ b/Firestore/core/include/firebase/firestore/timestamp.h @@ -19,6 +19,7 @@ #include #include +#include #include #if !defined(_STLPORT_VERSION) @@ -148,6 +149,8 @@ class Timestamp { * don't rely on the format of the string. */ std::string ToString() const; + friend std::ostream& operator<<(std::ostream& out, + const Timestamp& timestamp); private: // Checks that the number of seconds is within the supported date range, and diff --git a/Firestore/core/src/firebase/firestore/api/CMakeLists.txt b/Firestore/core/src/firebase/firestore/api/CMakeLists.txt new file mode 100644 index 00000000000..c50aa484a1f --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/CMakeLists.txt @@ -0,0 +1,48 @@ +# Copyright 2019 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + firebase_firestore_api_input_validation_std + SOURCES + input_validation.h + input_validation_std.cc + DEPENDS + firebase_firestore_util +) + +cc_library( + firebase_firestore_api_input_validation_apple + SOURCES + input_validation.h + input_validation_apple.mm + DEPENDS + firebase_firestore_util +) + +cc_select( + firebase_firestore_api_input_validation + APPLE firebase_firestore_api_input_validation_apple + DEFAULT firebase_firestore_api_input_validation_std +) + +cc_library( + firebase_firestore_api + SOURCES + snapshot_metadata.cc + snapshot_metadata.h + DEPENDS + absl_meta + firebase_firestore_api_input_validation + firebase_firestore_util +) diff --git a/Firestore/core/src/firebase/firestore/api/document_change.h b/Firestore/core/src/firebase/firestore/api/document_change.h new file mode 100644 index 00000000000..abc1d0d54a1 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/document_change.h @@ -0,0 +1,81 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_DOCUMENT_CHANGE_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_DOCUMENT_CHANGE_H_ + +#include + +#include "Firestore/core/src/firebase/firestore/api/document_snapshot.h" + +namespace firebase { +namespace firestore { +namespace api { + +class DocumentChange { + public: + enum class Type { Added, Modified, Removed }; + + DocumentChange() = default; + DocumentChange(Type type, + DocumentSnapshot document, + size_t old_index, + size_t new_index) + : type_(type), + document_(std::move(document)), + old_index_(old_index), + new_index_(new_index) { + } + + size_t Hash() const; + + Type type() const { + return type_; + } + + DocumentSnapshot document() const { + return document_; + } + + size_t old_index() const { + return old_index_; + } + + size_t new_index() const { + return new_index_; + } + + /** + * A sentinel return value for old_index() and new_index() indicating that + * there's no relevant index to return because the document was newly added + * or removed respectively. + */ + static constexpr size_t npos = static_cast(-1); + + private: + Type type_; + DocumentSnapshot document_; + size_t old_index_; + size_t new_index_; +}; + +bool operator==(const DocumentChange& lhs, const DocumentChange& rhs); + +} // namespace api +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_DOCUMENT_CHANGE_H_ diff --git a/Firestore/core/src/firebase/firestore/api/document_change.mm b/Firestore/core/src/firebase/firestore/api/document_change.mm new file mode 100644 index 00000000000..e6c2c485041 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/document_change.mm @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/api/document_change.h" + +#include "Firestore/core/src/firebase/firestore/util/hashing.h" + +namespace firebase { +namespace firestore { +namespace api { + +size_t DocumentChange::Hash() const { + return util::Hash(static_cast(type_), document_, old_index_, new_index_); +} + +bool operator==(const DocumentChange& lhs, const DocumentChange& rhs) { + return lhs.type() == rhs.type() && lhs.document() == rhs.document() && + lhs.old_index() == rhs.old_index() && + lhs.new_index() == rhs.new_index(); +} + +} // namespace api +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/api/document_reference.h b/Firestore/core/src/firebase/firestore/api/document_reference.h index 45f6bdb55b2..01c0121c4c4 100644 --- a/Firestore/core/src/firebase/firestore/api/document_reference.h +++ b/Firestore/core/src/firebase/firestore/api/document_reference.h @@ -27,37 +27,37 @@ #include #include -#import "FIRDocumentReference.h" -#import "FIRFirestoreSource.h" -#import "FIRListenerRegistration.h" - +#include "Firestore/core/src/firebase/firestore/api/document_snapshot.h" +#include "Firestore/core/src/firebase/firestore/api/listener_registration.h" +#include "Firestore/core/src/firebase/firestore/core/listen_options.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/util/statusor_callback.h" NS_ASSUME_NONNULL_BEGIN -@class FIRCollectionReference; -@class FIRFirestore; -@class FSTListenOptions; @class FSTMutation; namespace firebase { namespace firestore { namespace api { +class Firestore; +enum class Source; + class DocumentReference { public: using Completion = void (^)(NSError* _Nullable error) _Nullable; - using DocumentCompletion = void (^)(FIRDocumentSnapshot* _Nullable document, - NSError* _Nullable error) _Nullable; DocumentReference() = default; - DocumentReference(FIRFirestore* firestore, model::DocumentKey document_key) + DocumentReference(model::ResourcePath path, Firestore* firestore); + DocumentReference(model::DocumentKey document_key, Firestore* firestore) : firestore_{firestore}, key_{std::move(document_key)} { } size_t Hash() const; - FIRFirestore* firestore() const { + Firestore* firestore() const { return firestore_; } const model::DocumentKey& key() const { @@ -66,12 +66,14 @@ class DocumentReference { const std::string& document_id() const; - FIRCollectionReference* Parent() const; + // TODO(varconst) uncomment when core API CollectionReference is implemented. + // CollectionReference Parent() const; std::string Path() const; - FIRCollectionReference* GetCollectionReference( - const std::string& collection_path) const; + // TODO(varconst) uncomment when core API CollectionReference is implemented. + // CollectionReference GetCollectionReference( + // const std::string& collection_path) const; void SetData(std::vector&& mutations, Completion completion); @@ -79,13 +81,13 @@ class DocumentReference { void DeleteDocument(Completion completion); - void GetDocument(FIRFirestoreSource source, DocumentCompletion completion); + void GetDocument(Source source, DocumentSnapshot::Listener&& completion); - id AddSnapshotListener( - FIRDocumentSnapshotBlock listener, FSTListenOptions* options); + ListenerRegistration AddSnapshotListener( + core::ListenOptions options, DocumentSnapshot::Listener&& listener); private: - FIRFirestore* firestore_ = nil; + Firestore* firestore_ = nullptr; model::DocumentKey key_; }; diff --git a/Firestore/core/src/firebase/firestore/api/document_reference.mm b/Firestore/core/src/firebase/firestore/api/document_reference.mm index bd150a5fe1f..54598019e2c 100644 --- a/Firestore/core/src/firebase/firestore/api/document_reference.mm +++ b/Firestore/core/src/firebase/firestore/api/document_reference.mm @@ -16,28 +16,29 @@ #include "Firestore/core/src/firebase/firestore/api/document_reference.h" -#import "FIRSnapshotMetadata.h" +#include // NOLINT(build/c++11) +#include -#import "Firestore/Source/API/FIRCollectionReference+Internal.h" #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRListenerRegistration+Internal.h" #import "Firestore/Source/Core/FSTEventManager.h" #import "Firestore/Source/Core/FSTFirestoreClient.h" #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Source/Model/FSTMutation.h" -#import "Firestore/Source/Util/FSTAsyncQueryListener.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/source.h" #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/model/precondition.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/util/error_apple.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/hashing.h" #include "Firestore/core/src/firebase/firestore/util/objc_compatibility.h" +#include "Firestore/core/src/firebase/firestore/util/status.h" +#include "Firestore/core/src/firebase/firestore/util/statusor.h" NS_ASSUME_NONNULL_BEGIN @@ -46,13 +47,28 @@ namespace api { namespace objc = util::objc; +using core::AsyncEventListener; +using core::EventListener; using core::ViewSnapshot; -using core::ViewSnapshotHandler; using model::DocumentKey; using model::Precondition; using model::ResourcePath; using util::MakeNSError; +using util::Status; using util::StatusOr; +using util::StatusOrCallback; + +DocumentReference::DocumentReference(model::ResourcePath path, + Firestore* firestore) + : firestore_{firestore} { + if (path.size() % 2 != 0) { + HARD_FAIL( + "Invalid document reference. Document references must have an even " + "number of segments, but %s has %s", + path.CanonicalString(), path.size()); + } + key_ = DocumentKey{std::move(path)}; +} size_t DocumentReference::Hash() const { return util::Hash(firestore_, key_); @@ -62,160 +78,177 @@ return key_.path().last_segment(); } -FIRCollectionReference* DocumentReference::Parent() const { - return [FIRCollectionReference referenceWithPath:key_.path().PopLast() - firestore:firestore_]; -} +// TODO(varconst) uncomment when core API CollectionReference is implemented. +// CollectionReference DocumentReference::Parent() const { +// return CollectionReference{firestore_, key_.path().PopLast()}; +// } std::string DocumentReference::Path() const { return key_.path().CanonicalString(); } -FIRCollectionReference* DocumentReference::GetCollectionReference( - const std::string& collection_path) const { - ResourcePath sub_path = ResourcePath::FromString(collection_path); - ResourcePath path = key_.path().Append(sub_path); - return [FIRCollectionReference referenceWithPath:path firestore:firestore_]; -} +// TODO(varconst) uncomment when core API CollectionReference is implemented. +// CollectionReference DocumentReference::GetCollectionReference( +// const std::string& collection_path) const { +// ResourcePath sub_path = ResourcePath::FromString(collection_path); +// ResourcePath path = key_.path().Append(sub_path); +// return CollectionReference{firestore_, path}; +// } void DocumentReference::SetData(std::vector&& mutations, Completion completion) { - [firestore_.client writeMutations:std::move(mutations) completion:completion]; + [firestore_->client() writeMutations:std::move(mutations) + completion:completion]; } void DocumentReference::UpdateData(std::vector&& mutations, Completion completion) { - return [firestore_.client writeMutations:std::move(mutations) - completion:completion]; + return [firestore_->client() writeMutations:std::move(mutations) + completion:completion]; } void DocumentReference::DeleteDocument(Completion completion) { FSTDeleteMutation* mutation = [[FSTDeleteMutation alloc] initWithKey:key_ precondition:Precondition::None()]; - [firestore_.client writeMutations:{mutation} completion:completion]; + [firestore_->client() writeMutations:{mutation} completion:completion]; } -void DocumentReference::GetDocument(FIRFirestoreSource source, - DocumentCompletion completion) { - if (source == FIRFirestoreSourceCache) { - [firestore_.client getDocumentFromLocalCache:*this completion:completion]; +void DocumentReference::GetDocument(Source source, + DocumentSnapshot::Listener&& completion) { + if (source == Source::Cache) { + [firestore_->client() getDocumentFromLocalCache:*this + completion:std::move(completion)]; return; } - FSTListenOptions* options = - [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:true - includeDocumentMetadataChanges:true - waitForSyncWhenOnline:true]; - - // TODO(varconst): replace with a synchronization primitive that doesn't - // require libdispatch. See - // https://github.com/firebase/firebase-ios-sdk/blob/3ccbdcdc65c93c4621c045c3c6d15de9dcefa23f/Firestore/Source/Core/FSTFirestoreClient.mm#L161 - // for an example. - dispatch_semaphore_t registered = dispatch_semaphore_create(0); - __block id listener_registration; - FIRDocumentSnapshotBlock listener = ^(FIRDocumentSnapshot* snapshot, - NSError* error) { - if (error) { - completion(nil, error); - return; + ListenOptions options( + /*include_query_metadata_changes=*/true, + /*include_document_metadata_changes=*/true, + /*wait_for_sync_when_online=*/true); + + class ListenOnce : public EventListener { + public: + ListenOnce(Source source, DocumentSnapshot::Listener&& completion) + : source_(source), completion_(std::move(completion)) { } - // Remove query first before passing event to user to avoid user actions - // affecting the now stale query. - dispatch_semaphore_wait(registered, DISPATCH_TIME_FOREVER); - [listener_registration remove]; - - if (!snapshot.exists && snapshot.metadata.fromCache) { - // TODO(dimond): Reconsider how to raise missing documents when offline. - // If we're online and the document doesn't exist then we call the - // completion with a document with document.exists set to false. If we're - // offline however, we call the completion handler with an error. Two - // options: - // 1) Cache the negative response from the server so we can deliver that - // even when you're offline. - // 2) Actually call the completion handler with an error if the document - // doesn't exist when you are offline. - completion(nil, [NSError errorWithDomain:FIRFirestoreErrorDomain - code:FIRFirestoreErrorCodeUnavailable - userInfo:@{ - NSLocalizedDescriptionKey : - @"Failed to get document because " - @"the client is offline.", - }]); - } else if (snapshot.exists && snapshot.metadata.fromCache && - source == FIRFirestoreSourceServer) { - completion( - nil, - [NSError - errorWithDomain:FIRFirestoreErrorDomain - code:FIRFirestoreErrorCodeUnavailable - userInfo:@{ - NSLocalizedDescriptionKey : - @"Failed to get document from server. (However, " - @"this document does exist in the local cache. Run " - @"again without setting source to " - @"FIRFirestoreSourceServer to retrieve the cached " - @"document.)" - }]); - } else { - completion(snapshot, nil); + void OnEvent(StatusOr maybe_snapshot) override { + if (!maybe_snapshot.ok()) { + completion_->OnEvent(std::move(maybe_snapshot)); + return; + } + + DocumentSnapshot snapshot = std::move(maybe_snapshot).ValueOrDie(); + + // Remove query first before passing event to user to avoid user actions + // affecting the now stale query. + ListenerRegistration registration = + registration_promise_.get_future().get(); + registration.Remove(); + + if (!snapshot.exists() && snapshot.metadata().from_cache()) { + // TODO(dimond): Reconsider how to raise missing documents when + // offline. If we're online and the document doesn't exist then we + // call the completion with a document with document.exists set to + // false. If we're offline however, we call the completion handler + // with an error. Two options: 1) Cache the negative response from the + // server so we can deliver that even when you're offline. + // 2) Actually call the completion handler with an error if the + // document doesn't exist when you are offline. + completion_->OnEvent( + Status{FirestoreErrorCode::Unavailable, + "Failed to get document because the client is offline."}); + } else if (snapshot.exists() && snapshot.metadata().from_cache() && + source_ == Source::Server) { + completion_->OnEvent( + Status{FirestoreErrorCode::Unavailable, + "Failed to get document from server. (However, " + "this document does exist in the local cache. Run " + "again without setting source to " + "FirestoreSourceServer to retrieve the cached " + "document.)"}); + } else { + completion_->OnEvent(std::move(snapshot)); + } + } + + void Resolve(ListenerRegistration&& registration) { + registration_promise_.set_value(std::move(registration)); } + + private: + Source source_; + DocumentSnapshot::Listener completion_; + + std::promise registration_promise_; }; + auto listener = absl::make_unique(source, std::move(completion)); + auto listener_unowned = listener.get(); - listener_registration = AddSnapshotListener(listener, options); - dispatch_semaphore_signal(registered); + ListenerRegistration registration = + AddSnapshotListener(std::move(options), std::move(listener)); + + listener_unowned->Resolve(std::move(registration)); } -id DocumentReference::AddSnapshotListener( - FIRDocumentSnapshotBlock listener, FSTListenOptions* options) { - FIRFirestore* firestore = firestore_; +ListenerRegistration DocumentReference::AddSnapshotListener( + ListenOptions options, DocumentSnapshot::Listener&& user_listener) { FSTQuery* query = [FSTQuery queryWithPath:key_.path()]; - DocumentKey key = key_; - - ViewSnapshotHandler handler = - [key, listener, firestore](const StatusOr& maybe_snapshot) { - if (!maybe_snapshot.ok()) { - listener(nil, MakeNSError(maybe_snapshot.status())); - return; - } - - const ViewSnapshot& snapshot = maybe_snapshot.ValueOrDie(); - HARD_ASSERT(snapshot.documents().count <= 1, - "Too many document returned on a document query"); - FSTDocument* document = [snapshot.documents() documentForKey:key]; - - bool has_pending_writes = - document - ? snapshot.mutated_keys().contains(key) - // We don't raise `has_pending_writes` for deleted documents. - : false; - - FIRDocumentSnapshot* result = - [FIRDocumentSnapshot snapshotWithFirestore:firestore - documentKey:key - document:document - fromCache:snapshot.from_cache() - hasPendingWrites:has_pending_writes]; - listener(result, nil); - }; - - FSTAsyncQueryListener* async_listener = [[FSTAsyncQueryListener alloc] - initWithExecutor:firestore_.client.userExecutor - snapshotHandler:std::move(handler)]; - - FSTQueryListener* internal_listener = - [firestore.client listenToQuery:query - options:options - viewSnapshotHandler:[async_listener asyncSnapshotHandler]]; - return [[FSTListenerRegistration alloc] initWithClient:firestore_.client - asyncListener:async_listener - internalListener:internal_listener]; + + // Convert from ViewSnapshots to DocumentSnapshots. + class Converter : public EventListener { + public: + Converter(DocumentReference* parent, + DocumentSnapshot::Listener&& user_listener) + : firestore_(parent->firestore_), + key_(parent->key_), + user_listener_(std::move(user_listener)) { + } + + void OnEvent(StatusOr maybe_snapshot) override { + if (!maybe_snapshot.ok()) { + user_listener_->OnEvent(maybe_snapshot.status()); + return; + } + + ViewSnapshot snapshot = std::move(maybe_snapshot).ValueOrDie(); + HARD_ASSERT(snapshot.documents().size() <= 1, + "Too many documents returned on a document query"); + FSTDocument* document = snapshot.documents().GetDocument(key_); + + bool has_pending_writes = + document ? snapshot.mutated_keys().contains(key_) + // We don't raise `has_pending_writes` for deleted documents. + : false; + + DocumentSnapshot result{firestore_, key_, document, snapshot.from_cache(), + has_pending_writes}; + user_listener_->OnEvent(std::move(result)); + } + + private: + Firestore* firestore_; + DocumentKey key_; + DocumentSnapshot::Listener user_listener_; + }; + auto view_listener = + absl::make_unique(this, std::move(user_listener)); + + // Call the view_listener on the user Executor. + auto async_listener = AsyncEventListener::Create( + firestore_->client().userExecutor, std::move(view_listener)); + + std::shared_ptr query_listener = + [firestore_->client() listenToQuery:query + options:options + listener:async_listener]; + return ListenerRegistration(firestore_->client(), std::move(async_listener), + std::move(query_listener)); } bool operator==(const DocumentReference& lhs, const DocumentReference& rhs) { - return objc::Equals(lhs.firestore(), rhs.firestore()) && - lhs.key() == rhs.key(); + return lhs.firestore() == rhs.firestore() && lhs.key() == rhs.key(); } } // namespace api diff --git a/Firestore/core/src/firebase/firestore/api/document_snapshot.h b/Firestore/core/src/firebase/firestore/api/document_snapshot.h index 3fa44697b0a..e0662e11535 100644 --- a/Firestore/core/src/firebase/firestore/api/document_snapshot.h +++ b/Firestore/core/src/firebase/firestore/api/document_snapshot.h @@ -23,30 +23,45 @@ #import +#include #include #include #import "Firestore/Source/Model/FSTFieldValue.h" +#include "Firestore/core/src/firebase/firestore/api/snapshot_metadata.h" +#include "Firestore/core/src/firebase/firestore/core/event_listener.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" NS_ASSUME_NONNULL_BEGIN -@class FIRFirestore; -@class FIRSnapshotMetadata; -@class FIRDocumentReference; @class FSTDocument; namespace firebase { namespace firestore { namespace api { +class DocumentReference; +class Firestore; + class DocumentSnapshot { public: + using Listener = std::unique_ptr>; + DocumentSnapshot() = default; - DocumentSnapshot(FIRFirestore* firestore, + DocumentSnapshot(Firestore* firestore, + model::DocumentKey document_key, + FSTDocument* _Nullable document, + SnapshotMetadata metadata) + : firestore_{firestore}, + internal_key_{std::move(document_key)}, + internal_document_{document}, + metadata_{std::move(metadata)} { + } + + DocumentSnapshot(Firestore* firestore, model::DocumentKey document_key, FSTDocument* _Nullable document, bool from_cache, @@ -54,8 +69,7 @@ class DocumentSnapshot { : firestore_{firestore}, internal_key_{std::move(document_key)}, internal_document_{document}, - from_cache_{from_cache}, - has_pending_writes_{has_pending_writes} { + metadata_{has_pending_writes, from_cache} { } size_t Hash() const; @@ -68,13 +82,16 @@ class DocumentSnapshot { } std::string document_id() const; - FIRDocumentReference* CreateReference() const; - FIRSnapshotMetadata* GetMetadata() const; + const SnapshotMetadata& metadata() const { + return metadata_; + } + + DocumentReference CreateReference() const; FSTObjectValue* _Nullable GetData() const; id _Nullable GetValue(const model::FieldPath& field_path) const; - FIRFirestore* firestore() const { + Firestore* firestore() const { return firestore_; } @@ -82,13 +99,10 @@ class DocumentSnapshot { const DocumentSnapshot& rhs); private: - FIRFirestore* firestore_ = nil; + Firestore* firestore_ = nullptr; model::DocumentKey internal_key_; FSTDocument* internal_document_ = nil; - bool from_cache_ = false; - bool has_pending_writes_ = false; - - mutable FIRSnapshotMetadata* cached_metadata_ = nil; + SnapshotMetadata metadata_; }; } // namespace api diff --git a/Firestore/core/src/firebase/firestore/api/document_snapshot.mm b/Firestore/core/src/firebase/firestore/api/document_snapshot.mm index 4d7d7ee5da2..f07885b3a2a 100644 --- a/Firestore/core/src/firebase/firestore/api/document_snapshot.mm +++ b/Firestore/core/src/firebase/firestore/api/document_snapshot.mm @@ -16,10 +16,7 @@ #include "Firestore/core/src/firebase/firestore/api/document_snapshot.h" -#import "Firestore/Source/API/FIRFirestore+Internal.h" - #import "Firestore/Source/API/FIRDocumentReference+Internal.h" -#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/Model/FSTDocument.h" #include "Firestore/core/src/firebase/firestore/util/hashing.h" @@ -36,28 +33,17 @@ using model::FieldPath; size_t DocumentSnapshot::Hash() const { - return util::Hash(firestore_, internal_key_, internal_document_, from_cache_, - has_pending_writes_); + return util::Hash(firestore_, internal_key_, internal_document_, metadata_); } -FIRDocumentReference* DocumentSnapshot::CreateReference() const { - return [FIRDocumentReference referenceWithKey:internal_key_ - firestore:firestore_]; +DocumentReference DocumentSnapshot::CreateReference() const { + return DocumentReference{internal_key_, firestore_}; } std::string DocumentSnapshot::document_id() const { return internal_key_.path().last_segment(); } -FIRSnapshotMetadata* DocumentSnapshot::GetMetadata() const { - if (!cached_metadata_) { - cached_metadata_ = [FIRSnapshotMetadata - snapshotMetadataWithPendingWrites:has_pending_writes_ - fromCache:from_cache_]; - } - return cached_metadata_; -} - FSTObjectValue* _Nullable DocumentSnapshot::GetData() const { return internal_document_ == nil ? nil : [internal_document_ data]; } @@ -67,11 +53,10 @@ } bool operator==(const DocumentSnapshot& lhs, const DocumentSnapshot& rhs) { - return objc::Equals(lhs.firestore_, rhs.firestore_) && + return lhs.firestore_ == rhs.firestore_ && lhs.internal_key_ == rhs.internal_key_ && objc::Equals(lhs.internal_document_, rhs.internal_document_) && - lhs.from_cache_ == rhs.from_cache_ && - lhs.has_pending_writes_ == rhs.has_pending_writes_; + lhs.metadata_ == rhs.metadata_; } } // namespace api diff --git a/Firestore/core/src/firebase/firestore/api/firestore.h b/Firestore/core/src/firebase/firestore/api/firestore.h new file mode 100644 index 00000000000..ccfc6b52141 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/firestore.h @@ -0,0 +1,131 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_FIRESTORE_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_FIRESTORE_H_ + +#if !defined(__OBJC__) +#error "This header only supports Objective-C++" +#endif // !defined(__OBJC__) + +#import + +#include +#include // NOLINT(build/c++11) +#include +#include +#include "dispatch/dispatch.h" + +#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" +#include "Firestore/core/src/firebase/firestore/model/database_id.h" +#include "Firestore/core/src/firebase/firestore/util/async_queue.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" + +NS_ASSUME_NONNULL_BEGIN + +@class FIRApp; +@class FIRCollectionReference; +@class FIRFirestore; +@class FIRFirestoreSettings; +@class FIRQuery; +@class FIRTransaction; +@class FIRWriteBatch; +@class FSTFirestoreClient; + +namespace firebase { +namespace firestore { +namespace api { + +class DocumentReference; + +class Firestore { + public: + using TransactionBlock = id _Nullable (^)(FIRTransaction*, NSError** error); + using ErrorCompletion = void (^)(NSError* _Nullable error); + using ResultOrErrorCompletion = void (^)(id _Nullable result, + NSError* _Nullable error); + + Firestore() = default; + + Firestore(std::string project_id, + std::string database, + std::string persistence_key, + std::unique_ptr credentials_provider, + std::unique_ptr worker_queue, + void* extension); + + const model::DatabaseId& database_id() const { + return database_id_; + } + + const std::string& persistence_key() const { + return persistence_key_; + } + + FSTFirestoreClient* client() { + HARD_ASSERT(client_, "Client is not yet configured."); + return client_; + } + + util::AsyncQueue* worker_queue(); + + void* extension() { + return extension_; + } + + FIRFirestoreSettings* settings() const; + void set_settings(FIRFirestoreSettings* settings); + + FIRCollectionReference* GetCollection(absl::string_view collection_path); + DocumentReference GetDocument(absl::string_view document_path); + FIRWriteBatch* GetBatch(); + FIRQuery* GetCollectionGroup(NSString* collection_id); + + void RunTransaction(TransactionBlock update_block, + dispatch_queue_t queue, + ResultOrErrorCompletion completion); + + void Shutdown(ErrorCompletion completion); + + void EnableNetwork(ErrorCompletion completion); + void DisableNetwork(ErrorCompletion completion); + + private: + void EnsureClientConfigured(); + + model::DatabaseId database_id_; + std::unique_ptr credentials_provider_; + std::string persistence_key_; + FSTFirestoreClient* client_ = nil; + + // Ownership will be transferred to `FSTFirestoreClient` as soon as the + // client is created. + std::unique_ptr worker_queue_; + + void* extension_ = nullptr; + + FIRFirestoreSettings* settings_ = nil; + + mutable std::mutex mutex_; +}; + +} // namespace api +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_FIRESTORE_H_ diff --git a/Firestore/core/src/firebase/firestore/api/firestore.mm b/Firestore/core/src/firebase/firestore/api/firestore.mm new file mode 100644 index 00000000000..e248dbc4ba9 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/firestore.mm @@ -0,0 +1,202 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/api/firestore.h" + +#import "FIRFirestoreSettings.h" +#import "Firestore/Source/API/FIRCollectionReference+Internal.h" +#import "Firestore/Source/API/FIRDocumentReference+Internal.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" +#import "Firestore/Source/API/FIRQuery+Internal.h" +#import "Firestore/Source/API/FIRTransaction+Internal.h" +#import "Firestore/Source/API/FIRWriteBatch+Internal.h" +#import "Firestore/Source/Core/FSTFirestoreClient.h" +#import "Firestore/Source/Core/FSTQuery.h" + +#include "Firestore/core/src/firebase/firestore/api/document_reference.h" +#include "Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h" +#include "Firestore/core/src/firebase/firestore/core/transaction.h" +#include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/util/async_queue.h" +#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "absl/memory/memory.h" + +namespace firebase { +namespace firestore { +namespace api { + +using firebase::firestore::api::Firestore; +using firebase::firestore::auth::CredentialsProvider; +using firebase::firestore::core::DatabaseInfo; +using firebase::firestore::core::Transaction; +using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::ResourcePath; +using util::AsyncQueue; +using util::Executor; +using util::ExecutorLibdispatch; + +Firestore::Firestore(std::string project_id, + std::string database, + std::string persistence_key, + std::unique_ptr credentials_provider, + std::unique_ptr worker_queue, + void* extension) + : database_id_{std::move(project_id), std::move(database)}, + credentials_provider_{std::move(credentials_provider)}, + persistence_key_{std::move(persistence_key)}, + worker_queue_{std::move(worker_queue)}, + extension_{extension} { + settings_ = [[FIRFirestoreSettings alloc] init]; +} + +AsyncQueue* Firestore::worker_queue() { + return [client_ workerQueue]; +} + +FIRFirestoreSettings* Firestore::settings() const { + std::lock_guard lock{mutex_}; + // Disallow mutation of our internal settings + return [settings_ copy]; +} + +void Firestore::set_settings(FIRFirestoreSettings* settings) { + std::lock_guard lock{mutex_}; + // As a special exception, don't throw if the same settings are passed + // repeatedly. This should make it more friendly to create a Firestore + // instance. + if (client_ && ![settings_ isEqual:settings]) { + HARD_FAIL( + "Firestore instance has already been started and its settings can " + "no longer be changed. You can only set settings before calling any " + "other methods on a Firestore instance."); + } + settings_ = [settings copy]; +} + +FIRCollectionReference* Firestore::GetCollection( + absl::string_view collection_path) { + EnsureClientConfigured(); + ResourcePath path = ResourcePath::FromString(collection_path); + return [FIRCollectionReference + referenceWithPath:path + firestore:[FIRFirestore recoverFromFirestore:this]]; +} + +DocumentReference Firestore::GetDocument(absl::string_view document_path) { + EnsureClientConfigured(); + return DocumentReference{ResourcePath::FromString(document_path), this}; +} + +FIRWriteBatch* Firestore::GetBatch() { + EnsureClientConfigured(); + FIRFirestore* wrapper = [FIRFirestore recoverFromFirestore:this]; + + return [FIRWriteBatch writeBatchWithFirestore:wrapper]; +} + +FIRQuery* Firestore::GetCollectionGroup(NSString* collection_id) { + EnsureClientConfigured(); + FIRFirestore* wrapper = [FIRFirestore recoverFromFirestore:this]; + + return + [FIRQuery referenceWithQuery:[FSTQuery queryWithPath:ResourcePath::Empty() + collectionGroup:collection_id] + firestore:wrapper]; +} + +void Firestore::RunTransaction(TransactionBlock update_block, + dispatch_queue_t queue, + ResultOrErrorCompletion completion) { + EnsureClientConfigured(); + FIRFirestore* wrapper = [FIRFirestore recoverFromFirestore:this]; + + FSTTransactionBlock wrapped_update = + ^(std::shared_ptr internal_transaction, + void (^internal_completion)(id _Nullable, NSError* _Nullable)) { + FIRTransaction* transaction = [FIRTransaction + transactionWithInternalTransaction:std::move(internal_transaction) + firestore:wrapper]; + + dispatch_async(queue, ^{ + NSError* _Nullable error = nil; + id _Nullable result = update_block(transaction, &error); + if (error) { + // Force the result to be nil in the case of an error, in case the + // user set both. + result = nil; + } + internal_completion(result, error); + }); + }; + + [client_ transactionWithRetries:5 + updateBlock:wrapped_update + completion:completion]; +} + +void Firestore::Shutdown(ErrorCompletion completion) { + if (!client_) { + if (completion) { + // We should be dispatching the callback on the user dispatch queue + // but if the client is nil here that queue was never created. + completion(nil); + } + } else { + [client_ shutdownWithCompletion:completion]; + } +} + +void Firestore::EnableNetwork(ErrorCompletion completion) { + EnsureClientConfigured(); + [client_ enableNetworkWithCompletion:completion]; +} + +void Firestore::DisableNetwork(ErrorCompletion completion) { + EnsureClientConfigured(); + [client_ disableNetworkWithCompletion:completion]; +} + +void Firestore::EnsureClientConfigured() { + std::lock_guard lock{mutex_}; + + if (!client_) { + // These values are validated elsewhere; this is just double-checking: + HARD_ASSERT(settings_.host, "FirestoreSettings.host cannot be nil."); + HARD_ASSERT(settings_.dispatchQueue, + "FirestoreSettings.dispatchQueue cannot be nil."); + + DatabaseInfo database_info(database_id_, persistence_key_, + util::MakeString(settings_.host), + settings_.sslEnabled); + + std::unique_ptr user_executor = + absl::make_unique(settings_.dispatchQueue); + + HARD_ASSERT(worker_queue_, "Expected non-null worker queue"); + client_ = + [FSTFirestoreClient clientWithDatabaseInfo:database_info + settings:settings_ + credentialsProvider:credentials_provider_.get() + userExecutor:std::move(user_executor) + workerQueue:std::move(worker_queue_)]; + } +} + +} // namespace api +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/api/input_validation.h b/Firestore/core/src/firebase/firestore/api/input_validation.h new file mode 100644 index 00000000000..50cf7111067 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/input_validation.h @@ -0,0 +1,80 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_INPUT_VALIDATION_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_INPUT_VALIDATION_H_ + +// Routines in this file are used to throw an exception (or crash, depending on +// platform) in response to API usage errors. Exceptions should only be used +// for programmer errors made by consumers of the SDK, e.g. invalid method +// arguments. +// +// These routines avoid conditional compilation in the caller and avoids lint +// warnings around actually throwing exceptions in source. The implementation +// chooses the best way to surface a logic error to the developer. +// +// For recoverable runtime errors, use util::Status, or in pure Objective-C +// code use an NSError** out-parameter. +// +// For internal programming errors, including internal argument checking, use +// HARD_ASSERT or HARD_FAIL(). + +#include + +#include "Firestore/core/src/firebase/firestore/util/string_format.h" + +namespace firebase { +namespace firestore { +namespace api { + +namespace impl { + +[[noreturn]] void ThrowIllegalState(const std::string& message); +[[noreturn]] void ThrowInvalidArgument(const std::string& message); + +} // namespace impl + +/** + * Throws an exception indicating that the user passed an invalid argument. + * + * Invalid argument is interpreted pretty broadly and can mean that the user + * made an incompatible chained method call while building up a larger + * structure, like a query. + */ +template +[[noreturn]] void ThrowInvalidArgument(const char* format, const FA&... args) { + impl::ThrowInvalidArgument(util::StringFormat(format, args...)); +} + +/** + * Throws an exception that indicates the user has attempted to use an API + * that's in an illegal state, usually by violating a precondition of the API + * call. + * + * Good uses of these are things like using a write batch after committing or + * trying to use Firestore without initializing FIRApp. Builder-style APIs that + * haven't done anything yet should likely just stick to ThrowInvalidArgument. + */ +template +[[noreturn]] void ThrowIllegalState(const char* format, const FA&... args) { + impl::ThrowIllegalState(util::StringFormat(format, args...)); +} + +} // namespace api +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_INPUT_VALIDATION_H_ diff --git a/Firestore/core/src/firebase/firestore/api/input_validation_apple.mm b/Firestore/core/src/firebase/firestore/api/input_validation_apple.mm new file mode 100644 index 00000000000..ba1db1c8ed7 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/input_validation_apple.mm @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" + +#import + +#include "Firestore/core/src/firebase/firestore/util/string_apple.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace api { +namespace impl { + +static NSException* MakeException(NSString* name, const std::string& message) { + return [[NSException alloc] initWithName:name + reason:util::WrapNSString(message) + userInfo:nil]; +} + +[[noreturn]] void ThrowIllegalState(const std::string& message) { + @throw MakeException(@"FIRIllegalStateException", message); // NOLINT +} + +[[noreturn]] void ThrowInvalidArgument(const std::string& message) { + @throw MakeException(@"FIRInvalidArgumentException", message); // NOLINT +} + +} // namespace impl +} // namespace api +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END diff --git a/Firestore/core/src/firebase/firestore/api/input_validation_std.cc b/Firestore/core/src/firebase/firestore/api/input_validation_std.cc new file mode 100644 index 00000000000..212271e800e --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/input_validation_std.cc @@ -0,0 +1,50 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" + +#include +#include + +#include "Firestore/core/src/firebase/firestore/util/log.h" + +namespace firebase { +namespace firestore { +namespace api { +namespace impl { + +namespace { + +template +[[noreturn]] void Throw(const char* kind, const T& error) { + LOG_ERROR("%s: %s", kind, error.what()); + std::abort(); +} + +} // namespace + +[[noreturn]] void ThrowIllegalState(const std::string& message) { + Throw("Illegal state", std::logic_error(message)); +} + +[[noreturn]] void ThrowInvalidArgument(const std::string& message) { + Throw("Invalid argument", std::invalid_argument(message)); +} + +} // namespace impl +} // namespace api +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/api/listener_registration.h b/Firestore/core/src/firebase/firestore/api/listener_registration.h new file mode 100644 index 00000000000..10fe4d0c316 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/listener_registration.h @@ -0,0 +1,93 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_LISTENER_REGISTRATION_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_LISTENER_REGISTRATION_H_ + +#if !defined(__OBJC__) +#error "This header only supports Objective-C++" +#endif // !defined(__OBJC__) + +#import + +#include +#include + +#include "Firestore/core/src/firebase/firestore/core/event_listener.h" +#include "Firestore/core/src/firebase/firestore/core/query_listener.h" + +@class FSTFirestoreClient; + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace api { + +/** + * An internal handle that encapsulates a user's ability to request that we + * stop listening to a query. When a user calls Remove(), ListenerRegistration + * will synchronously mute the listener and then send a request to the + * FirestoreClient to actually unlisten. + * + * ListenerRegistration will not automaticlaly stop listening if it is + * destroyed. We allow users to fire and forget listens if they never want to + * stop them. + * + * Getting shutdown code right is tricky so ListenerRegistration is very + * forgiving. It will tolerate: + * + * * Multiple calls to Remove(), + * * calls to Remove() after we send an error, + * * calls to Remove() even after deleting the App in which the listener was + * started. + */ +class ListenerRegistration { + public: + ListenerRegistration( + FSTFirestoreClient* client, + std::shared_ptr> + async_listener, + std::shared_ptr query_listener) + : client_(client), + async_listener_(std::move(async_listener)), + query_listener_(std::move(query_listener)) { + } + + /** + * Removes the listener being tracked by this FIRListenerRegistration. After + * the initial call, subsequent calls have no effect. + */ + void Remove(); + + private: + /** The client that was used to register this listen. */ + FSTFirestoreClient* client_ = nil; + + /** The async listener that is used to mute events synchronously. */ + std::weak_ptr> async_listener_; + + /** The internal QueryListener that can be used to unlisten the query. */ + std::weak_ptr query_listener_; +}; + +} // namespace api +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_LISTENER_REGISTRATION_H_ diff --git a/Firestore/core/src/firebase/firestore/api/listener_registration.mm b/Firestore/core/src/firebase/firestore/api/listener_registration.mm new file mode 100644 index 00000000000..b885953df71 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/listener_registration.mm @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/api/listener_registration.h" + +#import "Firestore/Source/Core/FSTFirestoreClient.h" + +namespace firebase { +namespace firestore { +namespace api { + +void ListenerRegistration::Remove() { + auto async_listener = async_listener_.lock(); + if (async_listener) { + async_listener->Mute(); + async_listener_.reset(); + } + + auto query_listener = query_listener_.lock(); + if (query_listener) { + [client_ removeListener:query_listener]; + query_listener_.reset(); + } + + client_ = nil; +} + +} // namespace api +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/api/query_snapshot.h b/Firestore/core/src/firebase/firestore/api/query_snapshot.h new file mode 100644 index 00000000000..7495636c45c --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/query_snapshot.h @@ -0,0 +1,114 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_QUERY_SNAPSHOT_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_QUERY_SNAPSHOT_H_ + +#if !defined(__OBJC__) +#error "This header only supports Objective-C++" +#endif // !defined(__OBJC__) + +#import + +#include +#include + +#include "Firestore/core/src/firebase/firestore/api/document_change.h" +#include "Firestore/core/src/firebase/firestore/api/document_snapshot.h" +#include "Firestore/core/src/firebase/firestore/api/snapshot_metadata.h" +#include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" + +NS_ASSUME_NONNULL_BEGIN + +@class FSTQuery; + +namespace firebase { +namespace firestore { +namespace api { + +/** + * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects. + */ +class QuerySnapshot { + public: + QuerySnapshot(Firestore* firestore, + FSTQuery* query, + core::ViewSnapshot&& snapshot, + SnapshotMetadata metadata) + : firestore_(firestore), + internal_query_(query), + snapshot_(std::move(snapshot)), + metadata_(std::move(metadata)) { + } + + size_t Hash() const; + + /** + * Indicates whether this `QuerySnapshot` is empty (contains no documents). + */ + bool empty() const { + return snapshot_.documents().empty(); + } + + /** The count of documents in this `QuerySnapshot`. */ + size_t size() const { + return snapshot_.documents().size(); + } + + Firestore* firestore() const { + return firestore_; + } + + FSTQuery* internal_query() const { + return internal_query_; + } + + /** + * Metadata about this snapshot, concerning its source and if it has local + * modifications. + */ + const SnapshotMetadata& metadata() const { + return metadata_; + } + + /** Iterates over the `DocumentSnapshots` that make up this query snapshot. */ + void ForEachDocument( + const std::function& callback) const; + + /** + * Iterates over the `DocumentChanges` representing the changes between + * the prior snapshot and this one. + */ + void ForEachChange(bool include_metadata_changes, + const std::function& callback) const; + + friend bool operator==(const QuerySnapshot& lhs, const QuerySnapshot& rhs); + + private: + Firestore* firestore_ = nullptr; + FSTQuery* internal_query_ = nil; + core::ViewSnapshot snapshot_; + SnapshotMetadata metadata_; +}; + +} // namespace api +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_QUERY_SNAPSHOT_H_ diff --git a/Firestore/core/src/firebase/firestore/api/query_snapshot.mm b/Firestore/core/src/firebase/firestore/api/query_snapshot.mm new file mode 100644 index 00000000000..f14cbb148c0 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/query_snapshot.mm @@ -0,0 +1,157 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/api/query_snapshot.h" + +#include + +#import "Firestore/Source/API/FIRDocumentChange+Internal.h" +#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" +#import "Firestore/Source/API/FIRQuery+Internal.h" +#import "Firestore/Source/Core/FSTQuery.h" +#import "Firestore/Source/Model/FSTDocument.h" + +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" +#include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "Firestore/core/src/firebase/firestore/util/objc_compatibility.h" +#include "absl/types/optional.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace api { + +namespace objc = util::objc; +using api::Firestore; +using core::DocumentViewChange; +using core::ViewSnapshot; +using model::DocumentSet; + +bool operator==(const QuerySnapshot& lhs, const QuerySnapshot& rhs) { + return lhs.firestore_ == rhs.firestore_ && + objc::Equals(lhs.internal_query_, rhs.internal_query_) && + lhs.snapshot_ == rhs.snapshot_ && lhs.metadata_ == rhs.metadata_; +} + +size_t QuerySnapshot::Hash() const { + return util::Hash(firestore_, internal_query_, snapshot_, metadata_); +} + +void QuerySnapshot::ForEachDocument( + const std::function& callback) const { + DocumentSet documentSet = snapshot_.documents(); + bool from_cache = metadata_.from_cache(); + + for (FSTDocument* document : documentSet) { + bool has_pending_writes = snapshot_.mutated_keys().contains(document.key); + DocumentSnapshot snap(firestore_, document.key, document, from_cache, + has_pending_writes); + callback(std::move(snap)); + } +} + +static DocumentChange::Type DocumentChangeTypeForChange( + const DocumentViewChange& change) { + switch (change.type()) { + case DocumentViewChange::Type::kAdded: + return DocumentChange::Type::Added; + case DocumentViewChange::Type::kModified: + case DocumentViewChange::Type::kMetadata: + return DocumentChange::Type::Modified; + case DocumentViewChange::Type::kRemoved: + return DocumentChange::Type::Removed; + } + + HARD_FAIL("Unknown DocumentViewChange::Type: %s", change.type()); +} + +void QuerySnapshot::ForEachChange( + bool include_metadata_changes, + const std::function& callback) const { + if (include_metadata_changes && snapshot_.excludes_metadata_changes()) { + ThrowInvalidArgument("To include metadata changes with your document " + "changes, you must call " + "addSnapshotListener(includeMetadataChanges:true)."); + } + + if (snapshot_.old_documents().empty()) { + // Special case the first snapshot because index calculation is easy and + // fast. Also all changes on the first snapshot are adds so there are also + // no metadata-only changes to filter out. + FSTDocument* last_document = nil; + size_t index = 0; + for (const DocumentViewChange& change : snapshot_.document_changes()) { + FSTDocument* doc = change.document(); + SnapshotMetadata metadata( + /*pending_writes=*/snapshot_.mutated_keys().contains(doc.key), + /*from_cache=*/snapshot_.from_cache()); + DocumentSnapshot document(firestore_, doc.key, doc, metadata); + + HARD_ASSERT(change.type() == DocumentViewChange::Type::kAdded, + "Invalid event type for first snapshot"); + HARD_ASSERT(!last_document || snapshot_.query().comparator( + last_document, change.document()) == + NSOrderedAscending, + "Got added events in wrong order"); + + callback(DocumentChange(DocumentChange::Type::Added, std::move(document), + DocumentChange::npos, index++)); + } + + } else { + // A DocumentSet that is updated incrementally as changes are applied to use + // to lookup the index of a document. + DocumentSet index_tracker = snapshot_.old_documents(); + for (const DocumentViewChange& change : snapshot_.document_changes()) { + if (!include_metadata_changes && + change.type() == DocumentViewChange::Type::kMetadata) { + continue; + } + + FSTDocument* doc = change.document(); + SnapshotMetadata metadata( + /*pending_writes=*/snapshot_.mutated_keys().contains(doc.key), + /*from_cache=*/snapshot_.from_cache()); + DocumentSnapshot document(firestore_, doc.key, doc, metadata); + + size_t old_index = DocumentChange::npos; + size_t new_index = DocumentChange::npos; + if (change.type() != DocumentViewChange::Type::kAdded) { + old_index = index_tracker.IndexOf(change.document().key); + HARD_ASSERT(old_index != DocumentSet::npos, + "Index for document not found"); + index_tracker = index_tracker.erase(change.document().key); + } + if (change.type() != DocumentViewChange::Type::kRemoved) { + index_tracker = index_tracker.insert(change.document()); + new_index = index_tracker.IndexOf(change.document().key); + } + + DocumentChange::Type type = DocumentChangeTypeForChange(change); + callback(DocumentChange(type, std::move(document), old_index, new_index)); + } + } +} + +} // namespace api +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END diff --git a/Firestore/core/src/firebase/firestore/api/snapshot_metadata.cc b/Firestore/core/src/firebase/firestore/api/snapshot_metadata.cc new file mode 100644 index 00000000000..1790bfc6bf6 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/snapshot_metadata.cc @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/api/snapshot_metadata.h" + +#include "Firestore/core/src/firebase/firestore/util/hashing.h" + +namespace firebase { +namespace firestore { +namespace api { + +bool operator==(const SnapshotMetadata& lhs, const SnapshotMetadata& rhs) { + return lhs.pending_writes_ == rhs.pending_writes_ && + lhs.from_cache_ == rhs.from_cache_; +} + +size_t SnapshotMetadata::Hash() const { + return util::Hash(pending_writes_, from_cache_); +} + +} // namespace api +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/api/snapshot_metadata.h b/Firestore/core/src/firebase/firestore/api/snapshot_metadata.h new file mode 100644 index 00000000000..9fe2b808b4d --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/snapshot_metadata.h @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_SNAPSHOT_METADATA_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_SNAPSHOT_METADATA_H_ + +#include + +namespace firebase { +namespace firestore { +namespace api { + +/** Metadata about a snapshot, describing the state of the snapshot. */ +class SnapshotMetadata { + public: + SnapshotMetadata() = default; + SnapshotMetadata(bool pending_writes, bool from_cache) + : pending_writes_(pending_writes), from_cache_(from_cache) { + } + + /** + * Returns true if the snapshot contains the result of local writes (e.g. + * set() or update() calls) that have not yet been committed to the backend. + */ + bool pending_writes() const { + return pending_writes_; + } + + /** + * Returns true if the snapshot was created from cached data rather than + * guaranteed up-to-date server data. + */ + bool from_cache() const { + return from_cache_; + } + + friend bool operator==(const SnapshotMetadata& lhs, + const SnapshotMetadata& rhs); + + size_t Hash() const; + + private: + bool pending_writes_ = false; + bool from_cache_ = false; +}; + +} // namespace api +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_SNAPSHOT_METADATA_H_ diff --git a/Firestore/core/src/firebase/firestore/api/source.h b/Firestore/core/src/firebase/firestore/api/source.h new file mode 100644 index 00000000000..be1ec0de9fa --- /dev/null +++ b/Firestore/core/src/firebase/firestore/api/source.h @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_SOURCE_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_SOURCE_H_ + +namespace firebase { +namespace firestore { +namespace api { + +/** + * An enum that configures the behavior of `DocumentReference.GetDocument()` and + * `Query.GetDocuments()`. By providing a source enum the `GetDocument[s]` + * methods can be configured to fetch results only from the server, only from + * the local cache, or attempt to fetch results from the server and fall back to + * the cache (which is the default). + * + * See `FIRFirestoreSource` for more details. + */ +enum class Source { Default, Server, Cache }; + +} // namespace api +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_API_SOURCE_H_ diff --git a/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm index f167d3f8d67..582e8374dde 100644 --- a/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm +++ b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm @@ -22,13 +22,10 @@ #import #import +#include "Firestore/core/src/firebase/firestore/util/error_apple.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" -// NB: This is also defined in Firestore/Source/Public/FIRFirestoreErrors.h -// NOLINTNEXTLINE: public constant -NSString* const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain"; - namespace firebase { namespace firestore { namespace auth { diff --git a/Firestore/core/src/firebase/firestore/core/CMakeLists.txt b/Firestore/core/src/firebase/firestore/core/CMakeLists.txt index 6e734049fdc..2481596abca 100644 --- a/Firestore/core/src/firebase/firestore/core/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/core/CMakeLists.txt @@ -19,6 +19,7 @@ cc_library( database_info.h filter.cc filter.h + listen_options.h target_id_generator.cc target_id_generator.h query.cc diff --git a/Firestore/core/src/firebase/firestore/core/event_listener.h b/Firestore/core/src/firebase/firestore/core/event_listener.h new file mode 100644 index 00000000000..dcb9b8013dc --- /dev/null +++ b/Firestore/core/src/firebase/firestore/core/event_listener.h @@ -0,0 +1,144 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_EVENT_LISTENER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_EVENT_LISTENER_H_ + +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/util/executor.h" +#include "Firestore/core/src/firebase/firestore/util/statusor.h" +#include "Firestore/core/src/firebase/firestore/util/statusor_callback.h" +#include "absl/memory/memory.h" + +namespace firebase { +namespace firestore { +namespace core { + +/** + * A general interface for listening to events internally. + */ +template +class EventListener { + public: + static std::unique_ptr> Create( + util::StatusOrCallback callback); + + virtual ~EventListener() { + } + + /** + * OnEvent will be called with the new value or the error if an error + * occurred. + * + * @param maybe_value The value of the event or the error. + */ + virtual void OnEvent(util::StatusOr maybe_value) = 0; +}; + +/** + * A wrapper around another EventListener that dispatches events asynchronously. + */ +template +class AsyncEventListener + : public EventListener, + public std::enable_shared_from_this> { + public: + using DelegateListener = std::unique_ptr>; + + AsyncEventListener(util::Executor* executor, DelegateListener&& delegate) + : executor_(executor), delegate_(std::move(delegate)) { + // std::atomic's constructor is not atomic, so assign after contruction + // (since assignment is atomic). + muted_ = false; + } + + static std::shared_ptr> Create( + util::Executor* executor, DelegateListener&& delegate); + + static std::shared_ptr> Create( + util::Executor* executor, EventListener&& delegate) { + return Create(executor, + absl::make_unique(std::move(delegate))); + } + + void OnEvent(util::StatusOr maybe_value) override; + + /** + * Synchronously mutes the listener and raises no further events. This method + * is thread safe and can be called from any queue. + */ + void Mute(); + + private: + std::atomic muted_; + util::Executor* executor_; + DelegateListener delegate_; +}; + +template +std::unique_ptr> EventListener::Create( + util::StatusOrCallback callback) { + class CallbackEventListener : public EventListener { + public: + explicit CallbackEventListener(util::StatusOrCallback&& callback) + : callback_(std::move(callback)) { + } + + void OnEvent(util::StatusOr maybe_value) override { + callback_(std::move(maybe_value)); + } + + private: + util::StatusOrCallback callback_; + }; + + return absl::make_unique(std::move(callback)); +} + +template +std::shared_ptr> AsyncEventListener::Create( + util::Executor* executor, DelegateListener&& delegate) { + return std::make_shared>(executor, std::move(delegate)); +} + +template +void AsyncEventListener::Mute() { + muted_ = true; +} + +template +void AsyncEventListener::OnEvent(util::StatusOr maybe_value) { + // Retain a strong reference to this. If the EventManager is sending an error + // it will immediately clear its strong reference to this after posting the + // event. The strong reference here allows the AsyncEventListener to survive + // until the executor gets around to calling. + std::shared_ptr> shared_this = this->shared_from_this(); + + executor_->Execute([shared_this, maybe_value]() { + if (!shared_this->muted_) { + shared_this->delegate_->OnEvent(std::move(maybe_value)); + } + }); +} + +} // namespace core +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_EVENT_LISTENER_H_ diff --git a/Firestore/core/src/firebase/firestore/core/filter.h b/Firestore/core/src/firebase/firestore/core/filter.h index 47485667e17..8f6cadd2b63 100644 --- a/Firestore/core/src/firebase/firestore/core/filter.h +++ b/Firestore/core/src/firebase/firestore/core/filter.h @@ -31,12 +31,18 @@ namespace core { /** Interface used for all query filters. All filters are immutable. */ class Filter { public: + /** + * Operator is a value relation operator that can be used to filter documents. + * It is similar to NSPredicateOperatorType, but only has operators supported + * by Firestore. + */ enum class Operator { LessThan, LessThanOrEqual, Equal, - GreaterThan, GreaterThanOrEqual, + GreaterThan, + ArrayContains, }; /** diff --git a/Firestore/core/src/firebase/firestore/core/listen_options.h b/Firestore/core/src/firebase/firestore/core/listen_options.h new file mode 100644 index 00000000000..913fe8693d2 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/core/listen_options.h @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_LISTEN_OPTIONS_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_LISTEN_OPTIONS_H_ + +namespace firebase { +namespace firestore { +namespace core { + +class ListenOptions { + public: + ListenOptions() = default; + + /** + * Creates a new ListenOptions. + * + * @param include_query_metadata_changes Raise events when only metadata of + * the query changes. + * @param include_document_metadata_changes Raise events when only metadata of + * documents changes. + * @param wait_for_sync_when_online Wait for a sync with the server when + * online, but still raise events while offline + */ + ListenOptions(bool include_query_metadata_changes, + bool include_document_metadata_changes, + bool wait_for_sync_when_online) + : include_query_metadata_changes_(include_query_metadata_changes), + include_document_metadata_changes_(include_document_metadata_changes), + wait_for_sync_when_online_(wait_for_sync_when_online) { + } + + /** + * Creates a default ListenOptions, with metadata changes and + * wait_for_sync_when_online disabled. + */ + static ListenOptions DefaultOptions() { + return ListenOptions( + /*include_query_metadata_changes=*/false, + /*include_document_metadata_changes=*/false, + /*wait_for_sync_when_online=*/false); + } + + /** + * Creates a ListenOptions which optionally includes both query and document + * metadata changes. + */ + static ListenOptions FromIncludeMetadataChanges( + bool include_metadata_changes) { + return ListenOptions( + /*include_query_metadata_changes=*/include_metadata_changes, + /*include_document_metadata_changes=*/include_metadata_changes, + /*wait_for_sync_when_online=*/false); + } + + bool include_query_metadata_changes() const { + return include_query_metadata_changes_; + } + + bool include_document_metadata_changes() const { + return include_document_metadata_changes_; + } + + bool wait_for_sync_when_online() const { + return wait_for_sync_when_online_; + } + + private: + bool include_query_metadata_changes_ = false; + bool include_document_metadata_changes_ = false; + bool wait_for_sync_when_online_ = false; +}; + +} // namespace core +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_LISTEN_OPTIONS_H_ diff --git a/Firestore/core/src/firebase/firestore/core/query.h b/Firestore/core/src/firebase/firestore/core/query.h index 41d0df136bf..1d9bb37a6b9 100644 --- a/Firestore/core/src/firebase/firestore/core/query.h +++ b/Firestore/core/src/firebase/firestore/core/query.h @@ -92,6 +92,8 @@ class Query { // existing filters, plus the new one. (Both Query and Filter objects are // immutable.) Filters are not shared across unrelated Query instances. std::vector> filters_; + + // TODO(rsgowman): Port collection group queries logic. }; inline bool operator==(const Query& lhs, const Query& rhs) { diff --git a/Firestore/core/src/firebase/firestore/core/query_listener.h b/Firestore/core/src/firebase/firestore/core/query_listener.h new file mode 100644 index 00000000000..60cf13ccd4b --- /dev/null +++ b/Firestore/core/src/firebase/firestore/core/query_listener.h @@ -0,0 +1,134 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_QUERY_LISTENER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_QUERY_LISTENER_H_ + +#if !defined(__OBJC__) +#error "This header only supports Objective-C++" +#endif // !defined(__OBJC__) + +#import + +#include +#include + +#include "Firestore/core/src/firebase/firestore/core/listen_options.h" +#include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include "Firestore/core/src/firebase/firestore/model/types.h" +#include "Firestore/core/src/firebase/firestore/util/status.h" +#include "Firestore/core/src/firebase/firestore/util/statusor_callback.h" +#include "absl/types/optional.h" + +@class FSTQuery; + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace core { + +/** + * QueryListener takes a series of internal view snapshots and determines when + * to raise user-facing events. + */ +class QueryListener { + public: + static std::shared_ptr Create( + FSTQuery* query, + ListenOptions options, + ViewSnapshot::SharedListener&& listener) { + return std::make_shared(query, std::move(options), + std::move(listener)); + } + + static std::shared_ptr Create( + FSTQuery* query, ViewSnapshot::SharedListener&& listener) { + return Create(query, ListenOptions::DefaultOptions(), std::move(listener)); + } + + static std::shared_ptr Create( + FSTQuery* query, + ListenOptions options, + util::StatusOrCallback&& listener) { + auto event_listener = + EventListener::Create(std::move(listener)); + return Create(query, std::move(options), std::move(event_listener)); + } + + static std::shared_ptr Create( + FSTQuery* query, util::StatusOrCallback&& listener) { + return Create(query, ListenOptions::DefaultOptions(), std::move(listener)); + } + + QueryListener(FSTQuery* query, + ListenOptions options, + ViewSnapshot::SharedListener&& listener) + : query_(query), + options_(std::move(options)), + listener_(std::move(listener)) { + } + virtual ~QueryListener() { + } + + FSTQuery* query() const { + return query_; + } + + /** The last received view snapshot. */ + const absl::optional& snapshot() const { + return snapshot_; + } + + virtual void OnViewSnapshot(ViewSnapshot snapshot); + virtual void OnError(util::Status error); + virtual void OnOnlineStateChanged(model::OnlineState online_state); + + private: + bool ShouldRaiseInitialEvent(const ViewSnapshot& snapshot, + model::OnlineState online_state) const; + bool ShouldRaiseEvent(const ViewSnapshot& snapshot) const; + void RaiseInitialEvent(const ViewSnapshot& snapshot); + + FSTQuery* query_ = nil; + ListenOptions options_; + + /** + * The EventListener that will process ViewSnapshots associated with this + * query listener. + */ + ViewSnapshot::SharedListener listener_; + + /** + * Initial snapshots (e.g. from cache) may not be propagated to the + * ViewSnapshotHandler. This flag is set to true once we've actually raised an + * event. + */ + bool raised_initial_event_ = false; + + /** The last online state this query listener got. */ + model::OnlineState online_state_ = model::OnlineState::Unknown; + + absl::optional snapshot_; +}; + +} // namespace core +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_QUERY_LISTENER_H_ diff --git a/Firestore/core/src/firebase/firestore/core/query_listener.mm b/Firestore/core/src/firebase/firestore/core/query_listener.mm new file mode 100644 index 00000000000..df901f83eb6 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/core/query_listener.mm @@ -0,0 +1,147 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "Firestore/core/src/firebase/firestore/core/query_listener.h" + +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/document_set.h" +#include "Firestore/core/src/firebase/firestore/util/error_apple.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "Firestore/core/src/firebase/firestore/util/status.h" +#include "absl/types/optional.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace core { + +using model::OnlineState; +using model::TargetId; +using util::MakeStatus; +using util::Status; + +void QueryListener::OnViewSnapshot(ViewSnapshot snapshot) { + HARD_ASSERT( + !snapshot.document_changes().empty() || snapshot.sync_state_changed(), + "We got a new snapshot with no changes?"); + + if (!options_.include_document_metadata_changes()) { + // Remove the metadata-only changes. + std::vector changes; + for (const DocumentViewChange& change : snapshot.document_changes()) { + if (change.type() != DocumentViewChange::Type::kMetadata) { + changes.push_back(change); + } + } + + snapshot = ViewSnapshot{snapshot.query(), + snapshot.documents(), + snapshot.old_documents(), + std::move(changes), + snapshot.mutated_keys(), + snapshot.from_cache(), + snapshot.sync_state_changed(), + /*excludes_metadata_changes=*/true}; + } + + if (!raised_initial_event_) { + if (ShouldRaiseInitialEvent(snapshot, online_state_)) { + RaiseInitialEvent(snapshot); + } + } else if (ShouldRaiseEvent(snapshot)) { + listener_->OnEvent(snapshot); + } + + snapshot_ = std::move(snapshot); +} + +void QueryListener::OnError(Status error) { + listener_->OnEvent(std::move(error)); +} + +void QueryListener::OnOnlineStateChanged(OnlineState online_state) { + online_state_ = online_state; + if (snapshot_.has_value() && !raised_initial_event_ && + ShouldRaiseInitialEvent(snapshot_.value(), online_state)) { + RaiseInitialEvent(snapshot_.value()); + } +} + +bool QueryListener::ShouldRaiseInitialEvent(const ViewSnapshot& snapshot, + OnlineState online_state) const { + HARD_ASSERT(!raised_initial_event_, "Determining whether to raise initial " + "event, but already had first event."); + + // Always raise the first event when we're synced + if (!snapshot.from_cache()) { + return true; + } + + // NOTE: We consider OnlineState::Unknown as online (it should become Offline + // or Online if we wait long enough). + bool maybe_online = online_state != OnlineState::Offline; + + // Don't raise the event if we're online, aren't synced yet (checked + // above) and are waiting for a sync. + if (options_.wait_for_sync_when_online() && maybe_online) { + HARD_ASSERT(snapshot.from_cache(), + "Waiting for sync, but snapshot is not from cache."); + return false; + } + + // Raise data from cache if we have any documents or we are offline + return !snapshot.documents().empty() || online_state == OnlineState::Offline; +} + +bool QueryListener::ShouldRaiseEvent(const ViewSnapshot& snapshot) const { + // We don't need to handle include_document_metadata_changes() here because + // the Metadata only changes have already been stripped out if needed. At this + // point the only changes we will see are the ones we should propagate. + if (!snapshot.document_changes().empty()) { + return true; + } + + bool has_pending_writes_changed = + snapshot_.has_value() && + snapshot_.value().has_pending_writes() != snapshot.has_pending_writes(); + if (snapshot.sync_state_changed() || has_pending_writes_changed) { + return options_.include_query_metadata_changes(); + } + + // Generally we should have hit one of the cases above, but it's possible to + // get here if there were only metadata docChanges and they got stripped out. + return false; +} + +void QueryListener::RaiseInitialEvent(const ViewSnapshot& snapshot) { + HARD_ASSERT(!raised_initial_event_, + "Trying to raise initial events for second time"); + + ViewSnapshot modified_snapshot = ViewSnapshot::FromInitialDocuments( + snapshot.query(), snapshot.documents(), snapshot.mutated_keys(), + snapshot.from_cache(), snapshot.excludes_metadata_changes()); + raised_initial_event_ = true; + listener_->OnEvent(std::move(modified_snapshot)); +} + +} // namespace core +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END diff --git a/Firestore/core/src/firebase/firestore/core/relation_filter.cc b/Firestore/core/src/firebase/firestore/core/relation_filter.cc index 07087e6e121..731d1f59259 100644 --- a/Firestore/core/src/firebase/firestore/core/relation_filter.cc +++ b/Firestore/core/src/firebase/firestore/core/relation_filter.cc @@ -17,7 +17,9 @@ #include "Firestore/core/src/firebase/firestore/core/relation_filter.h" #include +#include +#include "absl/algorithm/container.h" #include "absl/types/optional.h" namespace firebase { @@ -48,9 +50,16 @@ bool RelationFilter::Matches(const model::Document& doc) const { } bool RelationFilter::MatchesValue(const FieldValue& other) const { - // Only compare types with matching backend order (such as double and int). - return FieldValue::Comparable(other.type(), value_rhs_.type()) && - MatchesComparison(other); + if (op_ == Filter::Operator::ArrayContains) { + if (other.type() != FieldValue::Type::Array) return false; + + const std::vector& contents = other.array_value(); + return absl::c_linear_search(contents, value_rhs_); + } else { + // Only compare types with matching backend order (such as double and int). + return FieldValue::Comparable(other.type(), value_rhs_.type()) && + MatchesComparison(other); + } } bool RelationFilter::MatchesComparison(const FieldValue& other) const { @@ -65,6 +74,8 @@ bool RelationFilter::MatchesComparison(const FieldValue& other) const { return other > value_rhs_; case Operator::GreaterThanOrEqual: return other >= value_rhs_; + case Operator::ArrayContains: + HARD_FAIL("Should have been handled in MatchesValue()"); } UNREACHABLE(); } diff --git a/Firestore/core/src/firebase/firestore/core/user_data.mm b/Firestore/core/src/firebase/firestore/core/user_data.mm index 237e49f234c..7150d12b329 100644 --- a/Firestore/core/src/firebase/firestore/core/user_data.mm +++ b/Firestore/core/src/firebase/firestore/core/user_data.mm @@ -19,14 +19,15 @@ #include #import "Firestore/Source/Model/FSTMutation.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "absl/strings/match.h" namespace firebase { namespace firestore { namespace core { +using api::ThrowInvalidArgument; using model::DocumentKey; using model::FieldMask; using model::FieldPath; @@ -155,8 +156,8 @@ case UserDataSource::Argument: return false; default: - FSTThrowInvalidArgument(@"Unexpected case for UserDataSource: %d", - accumulator_->data_source()); + ThrowInvalidArgument("Unexpected case for UserDataSource: %s", + accumulator_->data_source()); } } @@ -175,9 +176,8 @@ absl::string_view designator{RESERVED_FIELD_DESIGNATOR}; if (write() && absl::StartsWith(segment, designator) && absl::EndsWith(segment, designator)) { - FSTThrowInvalidArgument(@"Document fields cannot begin and end with %s%s", - RESERVED_FIELD_DESIGNATOR, - FieldDescription().c_str()); + ThrowInvalidArgument("Document fields cannot begin and end with %s%s", + RESERVED_FIELD_DESIGNATOR, FieldDescription()); } } diff --git a/Firestore/core/src/firebase/firestore/core/view_snapshot.h b/Firestore/core/src/firebase/firestore/core/view_snapshot.h index 51306ba5c4a..5e4c7defa7a 100644 --- a/Firestore/core/src/firebase/firestore/core/view_snapshot.h +++ b/Firestore/core/src/firebase/firestore/core/view_snapshot.h @@ -22,20 +22,23 @@ #endif // !defined(__OBJC__) #include +#include +#include #include #include #include +#include "Firestore/core/src/firebase/firestore/core/event_listener.h" #include "Firestore/core/src/firebase/firestore/immutable/sorted_map.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/util/statusor.h" NS_ASSUME_NONNULL_BEGIN @class FSTDocument; @class FSTQuery; -@class FSTDocumentSet; namespace firebase { namespace firestore { @@ -99,22 +102,18 @@ class DocumentViewChangeSet { immutable::SortedMap change_map_; }; -class ViewSnapshot; - -using ViewSnapshotHandler = - std::function&)>; - /** * A view snapshot is an immutable capture of the results of a query and the * changes to them. */ class ViewSnapshot { public: - ViewSnapshot() = default; + using Listener = std::unique_ptr>; + using SharedListener = std::shared_ptr>; ViewSnapshot(FSTQuery* query, - FSTDocumentSet* documents, - FSTDocumentSet* old_documents, + model::DocumentSet documents, + model::DocumentSet old_documents, std::vector document_changes, model::DocumentKeySet mutated_keys, bool from_cache, @@ -126,7 +125,7 @@ class ViewSnapshot { * added. */ static ViewSnapshot FromInitialDocuments(FSTQuery* query, - FSTDocumentSet* documents, + model::DocumentSet documents, model::DocumentKeySet mutated_keys, bool from_cache, bool excludes_metadata_changes); @@ -137,12 +136,12 @@ class ViewSnapshot { } /** The documents currently known to be results of the query. */ - FSTDocumentSet* documents() const { + const model::DocumentSet& documents() const { return documents_; } /** The documents of the last snapshot. */ - FSTDocumentSet* old_documents() const { + const model::DocumentSet& old_documents() const { return old_documents_; } @@ -177,13 +176,14 @@ class ViewSnapshot { } std::string ToString() const; + friend std::ostream& operator<<(std::ostream& out, const ViewSnapshot& value); size_t Hash() const; private: FSTQuery* query_ = nil; - FSTDocumentSet* documents_ = nil; - FSTDocumentSet* old_documents_ = nil; + model::DocumentSet documents_; + model::DocumentSet old_documents_; std::vector document_changes_; model::DocumentKeySet mutated_keys_; diff --git a/Firestore/core/src/firebase/firestore/core/view_snapshot.mm b/Firestore/core/src/firebase/firestore/core/view_snapshot.mm index f063983375f..6663063298c 100644 --- a/Firestore/core/src/firebase/firestore/core/view_snapshot.mm +++ b/Firestore/core/src/firebase/firestore/core/view_snapshot.mm @@ -16,10 +16,12 @@ #include "Firestore/core/src/firebase/firestore/core/view_snapshot.h" +#include + #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentSet.h" +#include "Firestore/core/src/firebase/firestore/model/document_set.h" #include "Firestore/core/src/firebase/firestore/util/hashing.h" #include "Firestore/core/src/firebase/firestore/util/objc_compatibility.h" #include "Firestore/core/src/firebase/firestore/util/string_format.h" @@ -32,6 +34,7 @@ namespace objc = util::objc; using model::DocumentKey; using model::DocumentKeySet; +using model::DocumentSet; using util::StringFormat; // DocumentViewChange @@ -132,16 +135,16 @@ // ViewSnapshot ViewSnapshot::ViewSnapshot(FSTQuery* query, - FSTDocumentSet* documents, - FSTDocumentSet* old_documents, + DocumentSet documents, + DocumentSet old_documents, std::vector document_changes, model::DocumentKeySet mutated_keys, bool from_cache, bool sync_state_changed, bool excludes_metadata_changes) : query_{query}, - documents_{documents}, - old_documents_{old_documents}, + documents_{std::move(documents)}, + old_documents_{std::move(old_documents)}, document_changes_{std::move(document_changes)}, mutated_keys_{std::move(mutated_keys)}, from_cache_{from_cache}, @@ -151,21 +154,20 @@ ViewSnapshot ViewSnapshot::FromInitialDocuments( FSTQuery* query, - FSTDocumentSet* documents, + DocumentSet documents, DocumentKeySet mutated_keys, bool from_cache, bool excludes_metadata_changes) { std::vector view_changes; - for (FSTDocument* doc in documents.documentEnumerator) { + for (FSTDocument* doc : documents) { view_changes.emplace_back(doc, DocumentViewChange::Type::kAdded); } - return ViewSnapshot{ - query, documents, - /*old_documents=*/ - [FSTDocumentSet documentSetWithComparator:query.comparator], - std::move(view_changes), std::move(mutated_keys), from_cache, - /*sync_state_changed=*/true, excludes_metadata_changes}; + return ViewSnapshot{query, documents, + /*old_documents=*/ + DocumentSet{query.comparator}, std::move(view_changes), + std::move(mutated_keys), from_cache, + /*sync_state_changed=*/true, excludes_metadata_changes}; } std::string ViewSnapshot::ToString() const { @@ -173,25 +175,29 @@ "", - query(), documents(), old_documents(), + query(), documents_.ToString(), old_documents_.ToString(), objc::Description(document_changes()), from_cache(), mutated_keys().size(), sync_state_changed(), excludes_metadata_changes()); } +std::ostream& operator<<(std::ostream& out, const ViewSnapshot& value) { + return out << value.ToString(); +} + size_t ViewSnapshot::Hash() const { // Note: We are omitting `mutated_keys_` from the hash, since we don't have a // straightforward way to compute its hash value. Since `ViewSnapshot` is // currently not stored in any dictionaries, this has no side effects. - return util::Hash([query() hash], [documents() hash], [old_documents() hash], + return util::Hash([query() hash], documents(), old_documents(), document_changes(), from_cache(), sync_state_changed(), excludes_metadata_changes()); } bool operator==(const ViewSnapshot& lhs, const ViewSnapshot& rhs) { return objc::Equals(lhs.query(), rhs.query()) && - objc::Equals(lhs.documents(), rhs.documents()) && - objc::Equals(lhs.old_documents(), rhs.old_documents()) && + lhs.documents() == rhs.documents() && + lhs.old_documents() == rhs.old_documents() && lhs.document_changes() == rhs.document_changes() && lhs.from_cache() == rhs.from_cache() && lhs.mutated_keys() == rhs.mutated_keys() && diff --git a/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt index 207863ef9c6..4791ad180e0 100644 --- a/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt @@ -20,9 +20,9 @@ cc_library( llrb_node.h llrb_node_iterator.h map_entry.h + sorted_container.h + sorted_container.cc sorted_map.h - sorted_map_base.h - sorted_map_base.cc sorted_map_iterator.h sorted_set.h tree_sorted_map.h diff --git a/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h index a88c1bdf7ff..1a7076d0e8d 100644 --- a/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h +++ b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h @@ -27,7 +27,7 @@ #include "Firestore/core/src/firebase/firestore/immutable/keys_view.h" #include "Firestore/core/src/firebase/firestore/immutable/map_entry.h" -#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h" +#include "Firestore/core/src/firebase/firestore/immutable/sorted_container.h" #include "Firestore/core/src/firebase/firestore/util/comparison.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/range.h" diff --git a/Firestore/core/src/firebase/firestore/immutable/llrb_node.h b/Firestore/core/src/firebase/firestore/immutable/llrb_node.h index 80c2d865455..0046580364e 100644 --- a/Firestore/core/src/firebase/firestore/immutable/llrb_node.h +++ b/Firestore/core/src/firebase/firestore/immutable/llrb_node.h @@ -21,7 +21,7 @@ #include #include "Firestore/core/src/firebase/firestore/immutable/llrb_node_iterator.h" -#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h" +#include "Firestore/core/src/firebase/firestore/immutable/sorted_container.h" namespace firebase { namespace firestore { diff --git a/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.cc b/Firestore/core/src/firebase/firestore/immutable/sorted_container.cc similarity index 89% rename from Firestore/core/src/firebase/firestore/immutable/sorted_map_base.cc rename to Firestore/core/src/firebase/firestore/immutable/sorted_container.cc index 954bdb9cd73..637270d3f71 100644 --- a/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.cc +++ b/Firestore/core/src/firebase/firestore/immutable/sorted_container.cc @@ -14,18 +14,16 @@ * limitations under the License. */ -#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h" +#include "Firestore/core/src/firebase/firestore/immutable/sorted_container.h" namespace firebase { namespace firestore { namespace immutable { -namespace impl { // Define external storage for constants: +constexpr SortedContainer::size_type SortedContainer::npos; constexpr SortedMapBase::size_type SortedMapBase::kFixedSize; -constexpr SortedMapBase::size_type SortedMapBase::npos; -} // namespace impl } // namespace immutable } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h b/Firestore/core/src/firebase/firestore/immutable/sorted_container.h similarity index 77% rename from Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h rename to Firestore/core/src/firebase/firestore/immutable/sorted_container.h index a19bd778830..1cf5eb79c5c 100644 --- a/Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h +++ b/Firestore/core/src/firebase/firestore/immutable/sorted_container.h @@ -14,25 +14,25 @@ * limitations under the License. */ -#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_BASE_H_ -#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_BASE_H_ +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_CONTAINER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_CONTAINER_H_ #include namespace firebase { namespace firestore { namespace immutable { -namespace impl { /** - * A base class for implementing sorted maps, containing types and constants - * that don't depend upon the template parameters to the main class. + * A base class for implementing immutable sorted containers, containing types + * and constants that don't depend upon the template parameters to the main + * class. * * Note that this exists as a base class rather than as just a namespace in * order to make it possible for users of the SortedMap classes to avoid needing * to declare storage for each instantiation of the template. */ -class SortedMapBase { +class SortedContainer { public: /** * The type of size() methods on immutable collections. Note: @@ -42,6 +42,23 @@ class SortedMapBase { */ using size_type = uint32_t; + /** + * A sentinel return value that indicates not found. Functionally similar to + * std::string::npos. + */ + static constexpr size_type npos = static_cast(-1); +}; + +/** + * A base class for implementing sorted maps, containing types and constants + * that don't depend upon the template parameters to the main class. + * + * Note that this exists as a base class rather than as just a namespace in + * order to make it possible for users of the SortedMap classes to avoid needing + * to declare storage for each instantiation of the template. + */ +class SortedMapBase : public SortedContainer { + public: /** * The maximum size of an ArraySortedMap. * @@ -52,19 +69,11 @@ class SortedMapBase { * inserting and lookups. Feel free to empirically determine this constant, * but don't expect much gain in real world performance. */ - // TODO(wilhuff): actually use this for switching implementations. static constexpr size_type kFixedSize = 25; - - /** - * A sentinel return value that indicates not found. Functionally similar to - * std::string::npos. - */ - static constexpr size_type npos = static_cast(-1); }; -} // namespace impl } // namespace immutable } // namespace firestore } // namespace firebase -#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_BASE_H_ +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_CONTAINER_H_ diff --git a/Firestore/core/src/firebase/firestore/immutable/sorted_map.h b/Firestore/core/src/firebase/firestore/immutable/sorted_map.h index 7960f6588d3..f3c998bd80e 100644 --- a/Firestore/core/src/firebase/firestore/immutable/sorted_map.h +++ b/Firestore/core/src/firebase/firestore/immutable/sorted_map.h @@ -21,7 +21,7 @@ #include "Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h" #include "Firestore/core/src/firebase/firestore/immutable/keys_view.h" -#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h" +#include "Firestore/core/src/firebase/firestore/immutable/sorted_container.h" #include "Firestore/core/src/firebase/firestore/immutable/sorted_map_iterator.h" #include "Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h" #include "Firestore/core/src/firebase/firestore/util/comparison.h" @@ -36,7 +36,7 @@ namespace immutable { * has methods to efficiently create new maps that are mutations of it. */ template > -class SortedMap : public impl::SortedMapBase { +class SortedMap : public SortedMapBase { public: using key_type = K; using mapped_type = V; @@ -204,7 +204,7 @@ class SortedMap : public impl::SortedMapBase { tree_type result = tree_.erase(key); if (result.empty()) { // Flip back to the array representation for empty arrays. - return SortedMap{}; + return SortedMap{comparator()}; } return SortedMap{std::move(result)}; } diff --git a/Firestore/core/src/firebase/firestore/immutable/sorted_set.h b/Firestore/core/src/firebase/firestore/immutable/sorted_set.h index 0b64a167836..36b6e16ef03 100644 --- a/Firestore/core/src/firebase/firestore/immutable/sorted_set.h +++ b/Firestore/core/src/firebase/firestore/immutable/sorted_set.h @@ -20,8 +20,8 @@ #include #include +#include "Firestore/core/src/firebase/firestore/immutable/sorted_container.h" #include "Firestore/core/src/firebase/firestore/immutable/sorted_map.h" -#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h" #include "Firestore/core/src/firebase/firestore/util/comparison.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/hashing.h" @@ -46,7 +46,7 @@ template , typename V = impl::Empty, typename M = SortedMap> -class SortedSet { +class SortedSet : public SortedContainer { public: using size_type = typename M::size_type; using value_type = K; @@ -101,7 +101,7 @@ class SortedSet { return const_iterator{map_.min()}; } - const K& max() const { + const_iterator max() const { return const_iterator{map_.max()}; } diff --git a/Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h b/Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h index 9fd51c33643..9a1635c2902 100644 --- a/Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h +++ b/Firestore/core/src/firebase/firestore/immutable/tree_sorted_map.h @@ -26,7 +26,7 @@ #include "Firestore/core/src/firebase/firestore/immutable/keys_view.h" #include "Firestore/core/src/firebase/firestore/immutable/llrb_node.h" #include "Firestore/core/src/firebase/firestore/immutable/map_entry.h" -#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h" +#include "Firestore/core/src/firebase/firestore/immutable/sorted_container.h" #include "Firestore/core/src/firebase/firestore/util/comparator_holder.h" #include "Firestore/core/src/firebase/firestore/util/comparison.h" diff --git a/Firestore/core/src/firebase/firestore/local/CMakeLists.txt b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt index f27fcb2f82b..dbffa9e46c7 100644 --- a/Firestore/core/src/firebase/firestore/local/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt @@ -16,10 +16,18 @@ if(HAVE_LEVELDB) cc_library( firebase_firestore_local_persistence_leveldb SOURCES + leveldb_index_manager.h + #leveldb_index_manager.mm leveldb_key.cc leveldb_key.h leveldb_migrations.cc leveldb_migrations.h + leveldb_mutation_queue.h + #leveldb_mutation_queue.mm + leveldb_query_cache.h + #leveldb_query_cache.mm + leveldb_remote_document_cache.h + #leveldb_remote_document_cache.mm leveldb_transaction.cc leveldb_transaction.h leveldb_util.cc @@ -46,14 +54,29 @@ endif() cc_library( firebase_firestore_local SOURCES - document_reference.h - document_reference.cc + document_key_reference.h + document_key_reference.cc + index_manager.h + listen_sequence.h + local_documents_view.h + local_documents_view.mm local_serializer.h local_serializer.cc + memory_index_manager.cc + memory_index_manager.h + memory_mutation_queue.h + #memory_mutation_queue.mm + memory_query_cache.h + #memory_query_cache.mm + memory_remote_document_cache.h + #memory_remote_document_cache.mm + mutation_queue.h + query_cache.h query_data.cc query_data.h reference_set.cc reference_set.h + remote_document_cache.h DEPENDS # TODO(b/111328563) Force nanopb first to work around ODR violations protobuf-nanopb diff --git a/Firestore/core/src/firebase/firestore/local/document_reference.cc b/Firestore/core/src/firebase/firestore/local/document_key_reference.cc similarity index 74% rename from Firestore/core/src/firebase/firestore/local/document_reference.cc rename to Firestore/core/src/firebase/firestore/local/document_key_reference.cc index e3f1794ed18..61c1b718456 100644 --- a/Firestore/core/src/firebase/firestore/local/document_reference.cc +++ b/Firestore/core/src/firebase/firestore/local/document_key_reference.cc @@ -14,10 +14,11 @@ * limitations under the License. */ +#include "Firestore/core/src/firebase/firestore/local/document_key_reference.h" + #include #include -#include "Firestore/core/src/firebase/firestore/local/document_reference.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/util/comparison.h" #include "Firestore/core/src/firebase/firestore/util/hashing.h" @@ -29,22 +30,23 @@ namespace local { using model::DocumentKey; using util::ComparisonResult; -bool operator==(const DocumentReference& lhs, const DocumentReference& rhs) { +bool operator==(const DocumentKeyReference& lhs, + const DocumentKeyReference& rhs) { return lhs.key_ == rhs.key_ && lhs.ref_id_ == rhs.ref_id_; } -size_t DocumentReference::Hash() const { +size_t DocumentKeyReference::Hash() const { return util::Hash(key_.ToString(), ref_id_); } -std::string DocumentReference::ToString() const { - return util::StringFormat("", +std::string DocumentKeyReference::ToString() const { + return util::StringFormat("", key_.ToString(), ref_id_); } /** Sorts document references by key then ID. */ -bool DocumentReference::ByKey::operator()(const DocumentReference& lhs, - const DocumentReference& rhs) const { +bool DocumentKeyReference::ByKey::operator()( + const DocumentKeyReference& lhs, const DocumentKeyReference& rhs) const { util::Comparator key_less; if (key_less(lhs.key_, rhs.key_)) return true; if (key_less(rhs.key_, lhs.key_)) return false; @@ -54,8 +56,8 @@ bool DocumentReference::ByKey::operator()(const DocumentReference& lhs, } /** Sorts document references by ID then key. */ -bool DocumentReference::ById::operator()(const DocumentReference& lhs, - const DocumentReference& rhs) const { +bool DocumentKeyReference::ById::operator()( + const DocumentKeyReference& lhs, const DocumentKeyReference& rhs) const { util::Comparator id_less; if (id_less(lhs.ref_id_, rhs.ref_id_)) return true; if (id_less(rhs.ref_id_, lhs.ref_id_)) return false; diff --git a/Firestore/core/src/firebase/firestore/local/document_reference.h b/Firestore/core/src/firebase/firestore/local/document_key_reference.h similarity index 74% rename from Firestore/core/src/firebase/firestore/local/document_reference.h rename to Firestore/core/src/firebase/firestore/local/document_key_reference.h index 3160ca42788..257971b5b2d 100644 --- a/Firestore/core/src/firebase/firestore/local/document_reference.h +++ b/Firestore/core/src/firebase/firestore/local/document_key_reference.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_DOCUMENT_REFERENCE_H_ -#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_DOCUMENT_REFERENCE_H_ +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_DOCUMENT_KEY_REFERENCE_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_DOCUMENT_KEY_REFERENCE_H_ #include #include @@ -39,13 +39,13 @@ namespace local { * * Not to be confused with FIRDocumentReference. */ -class DocumentReference { +class DocumentKeyReference { public: - DocumentReference() { + DocumentKeyReference() { } /** Initializes the document reference with the given key and Id. */ - DocumentReference(model::DocumentKey key, int32_t ref_id) + DocumentKeyReference(model::DocumentKey key, int32_t ref_id) : key_{std::move(key)}, ref_id_{ref_id} { } @@ -63,23 +63,23 @@ class DocumentReference { return ref_id_; } - friend bool operator==(const DocumentReference& lhs, - const DocumentReference& rhs); + friend bool operator==(const DocumentKeyReference& lhs, + const DocumentKeyReference& rhs); size_t Hash() const; std::string ToString() const; /** Sorts document references by key then Id. */ - struct ByKey : public util::Comparator { - bool operator()(const DocumentReference& lhs, - const DocumentReference& rhs) const; + struct ByKey : public util::Comparator { + bool operator()(const DocumentKeyReference& lhs, + const DocumentKeyReference& rhs) const; }; /** Sorts document references by Id then key. */ - struct ById : public util::Comparator { - bool operator()(const DocumentReference& lhs, - const DocumentReference& rhs) const; + struct ById : public util::Comparator { + bool operator()(const DocumentKeyReference& lhs, + const DocumentKeyReference& rhs) const; }; private: @@ -94,4 +94,4 @@ class DocumentReference { } // namespace firestore } // namespace firebase -#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_DOCUMENT_REFERENCE_H_ +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_DOCUMENT_KEY_REFERENCE_H_ diff --git a/Firestore/core/src/firebase/firestore/local/index_manager.h b/Firestore/core/src/firebase/firestore/local/index_manager.h new file mode 100644 index 00000000000..c4bad32eb86 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/index_manager.h @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_INDEX_MANAGER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_INDEX_MANAGER_H_ + +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" + +namespace firebase { +namespace firestore { +namespace local { + +/** + * Represents a set of indexes that are used to execute queries efficiently. + * + * Currently the only index is a [collection id] => [parent path] index, used + * to execute Collection Group queries. + */ +class IndexManager { + public: + virtual ~IndexManager() { + } + + /** + * Creates an index entry mapping the collectionId (last segment of the path) + * to the parent path (either the containing document location or the empty + * path for root-level collections). Index entries can be retrieved via + * GetCollectionParents(). + * + * NOTE: Currently we don't remove index entries. If this ends up being an + * issue we can devise some sort of GC strategy. + */ + virtual void AddToCollectionParentIndex( + const model::ResourcePath& collection_path) = 0; + + /** + * Retrieves all parent locations containing the given collectionId, as a set + * of paths (each path being either a document location or the empty path for + * a root-level collection). + */ + virtual std::vector GetCollectionParents( + const std::string& collection_id) = 0; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_INDEX_MANAGER_H_ diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_index_manager.h b/Firestore/core/src/firebase/firestore/local/leveldb_index_manager.h new file mode 100644 index 00000000000..3b44cbd0d2e --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/leveldb_index_manager.h @@ -0,0 +1,70 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_INDEX_MANAGER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_INDEX_MANAGER_H_ + +#if !defined(__OBJC__) +#error "For now, this file must only be included by ObjC source files." +#endif // !defined(__OBJC__) + +#include +#include + +#include "Firestore/core/src/firebase/firestore/local/index_manager.h" +#include "Firestore/core/src/firebase/firestore/local/memory_index_manager.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" + +@class FSTLevelDB; + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace local { + +/** A persisted implementation of IndexManager. */ +class LevelDbIndexManager : public IndexManager { + public: + explicit LevelDbIndexManager(FSTLevelDB* db); + + void AddToCollectionParentIndex( + const model::ResourcePath& collection_path) override; + + std::vector GetCollectionParents( + const std::string& collection_id) override; + + private: + // This instance is owned by FSTLevelDB; avoid a retain cycle. + __weak FSTLevelDB* db_; + + /** + * An in-memory copy of the index entries we've already written since the SDK + * launched. Used to avoid re-writing the same entry repeatedly. + * + * This is *NOT* a complete cache of what's in persistence and so can never + * be used to satisfy reads. + */ + MemoryCollectionParentIndex collection_parents_cache_; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_INDEX_MANAGER_H_ diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_index_manager.mm b/Firestore/core/src/firebase/firestore/local/leveldb_index_manager.mm new file mode 100644 index 00000000000..ef7773b541e --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/leveldb_index_manager.mm @@ -0,0 +1,81 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/local/leveldb_index_manager.h" + +#include +#include + +#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h" +#include "Firestore/core/src/firebase/firestore/local/memory_index_manager.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "absl/strings/match.h" + +#import "Firestore/Source/Local/FSTLevelDB.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace local { + +using model::ResourcePath; + +LevelDbIndexManager::LevelDbIndexManager(FSTLevelDB* db) : db_(db) { +} + +void LevelDbIndexManager::AddToCollectionParentIndex( + const ResourcePath& collection_path) { + HARD_ASSERT(collection_path.size() % 2 == 1, "Expected a collection path."); + + if (collection_parents_cache_.Add(collection_path)) { + std::string collection_id = collection_path.last_segment(); + ResourcePath parent_path = collection_path.PopLast(); + + std::string key = + LevelDbCollectionParentKey::Key(collection_id, parent_path); + std::string empty_buffer; + db_.currentTransaction->Put(key, empty_buffer); + } +} + +std::vector LevelDbIndexManager::GetCollectionParents( + const std::string& collection_id) { + std::vector results; + + auto index_iterator = db_.currentTransaction->NewIterator(); + std::string index_prefix = + LevelDbCollectionParentKey::KeyPrefix(collection_id); + LevelDbCollectionParentKey row_key; + for (index_iterator->Seek(index_prefix); index_iterator->Valid(); + index_iterator->Next()) { + if (!absl::StartsWith(index_iterator->key(), index_prefix) || + !row_key.Decode(index_iterator->key()) || + row_key.collection_id() != collection_id) { + break; + } + + results.push_back(row_key.parent()); + } + return results; +} + +} // namespace local +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_key.cc b/Firestore/core/src/firebase/firestore/local/leveldb_key.cc index d0875dc65d5..e0f632896a2 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_key.cc +++ b/Firestore/core/src/firebase/firestore/local/leveldb_key.cc @@ -45,6 +45,7 @@ const char* kQueryTargetsTable = "query_target"; const char* kTargetDocumentsTable = "target_document"; const char* kDocumentTargetsTable = "document_target"; const char* kRemoteDocumentsTable = "remote_document"; +const char* kCollectionParentsTable = "collection_parent"; /** * Labels for the components of keys. These serve to make keys self-describing. @@ -89,6 +90,12 @@ enum ComponentLabel { /** A component containing a user Id. */ UserId = 13, + /** + * A component containing a standalone collection ID (e.g. as used by the + * collection_parent table, but not for collection IDs within paths). + */ + CollectionId = 14, + /** * A path segment describes just a single segment in a resource path. Path * segments that occur sequentially in a key represent successive segments in @@ -159,6 +166,17 @@ class Reader { return ReadLabeledString(ComponentLabel::UserId); } + std::string ReadCollectionId() { + return ReadLabeledString(ComponentLabel::CollectionId); + } + + /** + * Reads component labels and strings from the key until it finds a component + * label other than ComponentLabel::PathSegment (or the key is exhausted). + * All matched path segments are assembled into a ResourcePath. + */ + ResourcePath ReadResourcePath(); + /** * Reads component labels and strings from the key until it finds a component * label other than ComponentLabel::PathSegment (or the key is exhausted). @@ -358,7 +376,7 @@ class Reader { bool ok_; }; -DocumentKey Reader::ReadDocumentKey() { +ResourcePath Reader::ReadResourcePath() { std::vector path_segments; while (!empty()) { // Advance a temporary slice to avoid advancing contents into the next key @@ -375,7 +393,13 @@ DocumentKey Reader::ReadDocumentKey() { path_segments.push_back(std::move(segment)); } - ResourcePath path{std::move(path_segments)}; + return ResourcePath{std::move(path_segments)}; +} + +DocumentKey Reader::ReadDocumentKey() { + ResourcePath path = ReadResourcePath(); + + // Avoid assertion failures in DocumentKey if path is invalid. if (ok_ && !path.empty() && DocumentKey::IsDocumentKey(path)) { return DocumentKey{std::move(path)}; } @@ -419,10 +443,10 @@ std::string Reader::Describe() { src_ = saved_source; if (label == ComponentLabel::PathSegment) { - DocumentKey document_key = ReadDocumentKey(); + ResourcePath resource_path = ReadResourcePath(); if (ok_) { absl::StrAppend(&description, - " key=", document_key.path().CanonicalString()); + " path=", resource_path.CanonicalString()); } } else if (label == ComponentLabel::TableName) { @@ -455,6 +479,12 @@ std::string Reader::Describe() { absl::StrAppend(&description, " user_id=", user_id); } + } else if (label == ComponentLabel::CollectionId) { + std::string collection_id = ReadCollectionId(); + if (ok_) { + absl::StrAppend(&description, " collection_id=", collection_id); + } + } else { absl::StrAppend(&description, " unknown label=", static_cast(label)); Fail(); @@ -502,6 +532,10 @@ class Writer { WriteLabeledString(ComponentLabel::UserId, user_id); } + void WriteCollectionId(absl::string_view collection_id) { + WriteLabeledString(ComponentLabel::CollectionId, collection_id); + } + /** * For each segment in the given resource path writes a * ComponentLabel::PathSegment component label and a string containing the @@ -848,6 +882,39 @@ bool LevelDbRemoteDocumentKey::Decode(absl::string_view key) { return reader.ok(); } +std::string LevelDbCollectionParentKey::KeyPrefix() { + Writer writer; + writer.WriteTableName(kCollectionParentsTable); + return writer.result(); +} + +std::string LevelDbCollectionParentKey::KeyPrefix( + absl::string_view collection_id) { + Writer writer; + writer.WriteTableName(kCollectionParentsTable); + writer.WriteCollectionId(collection_id); + return writer.result(); +} + +std::string LevelDbCollectionParentKey::Key(absl::string_view collection_id, + const ResourcePath& parent) { + Writer writer; + writer.WriteTableName(kCollectionParentsTable); + writer.WriteCollectionId(collection_id); + writer.WriteResourcePath(parent); + writer.WriteTerminator(); + return writer.result(); +} + +bool LevelDbCollectionParentKey::Decode(absl::string_view key) { + Reader reader{key}; + reader.ReadTableNameMatching(kCollectionParentsTable); + collection_id_ = reader.ReadCollectionId(); + parent_ = reader.ReadResourcePath(); + reader.ReadTerminator(); + return reader.ok(); +} + } // namespace local } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_key.h b/Firestore/core/src/firebase/firestore/local/leveldb_key.h index 4bbf738e5ed..505cb871214 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_key.h +++ b/Firestore/core/src/firebase/firestore/local/leveldb_key.h @@ -77,6 +77,11 @@ namespace local { // remote_documents: // - table_name: string = "remote_document" // - path: ResourcePath +// +// collection_parents: +// - table_name: string = "collection_parent" +// - collectionId: string +// - parent: ResourcePath /** * Parses the given key and returns a human readable description of its @@ -520,6 +525,59 @@ class LevelDbRemoteDocumentKey { model::DocumentKey document_key_; }; +/** + * A key in the collection parents index, which stores an association between a + * Collection ID (e.g. 'messages') to a parent path (e.g. '/chats/123') that + * contains it as a (sub)collection. This is used to efficiently find all + * collections to query when performing a Collection Group query. Note that the + * parent path will be an empty path in the case of root-level collections. + */ +class LevelDbCollectionParentKey { + public: + /** + * Creates a key prefix that points just before the first key in the table. + */ + static std::string KeyPrefix(); + + /** + * Creates a key prefix that points just before the first key for the given + * collection_id. + */ + static std::string KeyPrefix(absl::string_view collection_id); + + /** + * Creates a complete key that points to a specific collection_id and parent. + */ + static std::string Key(absl::string_view collection_id, + const model::ResourcePath& parent); + + /** + * Decodes the given complete key, storing the decoded values in this + * instance. + * + * @return true if the key successfully decoded, false otherwise. If false is + * returned, this instance is in an undefined state until the next call to + * `Decode()`. + */ + ABSL_MUST_USE_RESULT + bool Decode(absl::string_view key); + + /** The collection_id, as encoded in the key. */ + const std::string& collection_id() const { + return collection_id_; + } + + /** The parent path, as encoded in the key. */ + const model::ResourcePath& parent() const { + return parent_; + } + + private: + // Deliberately uninitialized: will be assigned in Decode + std::string collection_id_; + model::ResourcePath parent_; +}; + } // namespace local } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_migrations.cc b/Firestore/core/src/firebase/firestore/local/leveldb_migrations.cc index 8b780a104d3..dda29e78354 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_migrations.cc +++ b/Firestore/core/src/firebase/firestore/local/leveldb_migrations.cc @@ -22,6 +22,7 @@ #include "Firestore/Protos/nanopb/firestore/local/mutation.nanopb.h" #include "Firestore/Protos/nanopb/firestore/local/target.nanopb.h" #include "Firestore/core/src/firebase/firestore/local/leveldb_key.h" +#include "Firestore/core/src/firebase/firestore/local/memory_index_manager.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/types.h" #include "Firestore/core/src/firebase/firestore/nanopb/reader.h" @@ -36,6 +37,8 @@ using leveldb::Iterator; using leveldb::Slice; using leveldb::Status; using leveldb::WriteOptions; +using model::DocumentKey; +using model::ResourcePath; using nanopb::Reader; using nanopb::Writer; @@ -60,8 +63,9 @@ namespace { * * Migration 4 ensures that every document in the remote document cache * has a sentinel row with a sequence number. * * Migration 5 drops held write acks. + * * Migration 6 populates the collection_parents index. */ -const LevelDbMigrations::SchemaVersion kSchemaVersion = 5; +const LevelDbMigrations::SchemaVersion kSchemaVersion = 6; /** * Save the given version number as the current version of the schema of the @@ -214,6 +218,8 @@ void EnsureSentinelRow(LevelDbTransaction* transaction, } /** + * Migration 4. + * * Ensure each document in the remote document table has a corresponding * sentinel row in the document target index. */ @@ -241,6 +247,65 @@ void EnsureSentinelRows(leveldb::DB* db) { transaction.Commit(); } +// Helper to add an index entry iff we haven't already written it (as determined +// by the provided cache). +void EnsureCollectionParentRow(LevelDbTransaction* transaction, + MemoryCollectionParentIndex* cache, + const DocumentKey& key) { + const ResourcePath& collection_path = key.path().PopLast(); + if (cache->Add(collection_path)) { + std::string collection_id = collection_path.last_segment(); + ResourcePath parent_path = collection_path.PopLast(); + + std::string key = + LevelDbCollectionParentKey::Key(collection_id, parent_path); + std::string empty_buffer; + transaction->Put(key, empty_buffer); + } +} + +/** + * Migration 6. + * + * Creates appropriate LevelDbCollectionParentKey rows for all collections + * of documents in the remote document cache and mutation queue. + */ +void EnsureCollectionParentsIndex(leveldb::DB* db) { + LevelDbTransaction transaction(db, "Ensure Collection Parents Index"); + + MemoryCollectionParentIndex cache; + + // Index existing remote documents. + std::string documents_prefix = LevelDbRemoteDocumentKey::KeyPrefix(); + auto it = transaction.NewIterator(); + it->Seek(documents_prefix); + LevelDbRemoteDocumentKey document_key; + for (; it->Valid() && absl::StartsWith(it->key(), documents_prefix); + it->Next()) { + HARD_ASSERT(document_key.Decode(it->key()), + "Failed to decode document key"); + + EnsureCollectionParentRow(&transaction, &cache, + document_key.document_key()); + } + + // Index existing mutations. + std::string mutations_prefix = LevelDbDocumentMutationKey::KeyPrefix(); + it = transaction.NewIterator(); + it->Seek(mutations_prefix); + LevelDbDocumentMutationKey key; + for (; it->Valid() && absl::StartsWith(it->key(), mutations_prefix); + it->Next()) { + HARD_ASSERT(key.Decode(it->key()), + "Failed to decode document-mutation key"); + + EnsureCollectionParentRow(&transaction, &cache, key.document_key()); + } + + SaveVersion(6, &transaction); + transaction.Commit(); +} + } // namespace LevelDbMigrations::SchemaVersion LevelDbMigrations::ReadSchemaVersion( @@ -287,6 +352,10 @@ void LevelDbMigrations::RunMigrations(leveldb::DB* db, if (from_version < 5 && to_version >= 5) { RemoveAcknowledgedMutations(db); } + + if (from_version < 6 && to_version >= 6) { + EnsureCollectionParentsIndex(db); + } } } // namespace local diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_mutation_queue.mm b/Firestore/core/src/firebase/firestore/local/leveldb_mutation_queue.mm index 53928703206..0abb5f14bd1 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_mutation_queue.mm +++ b/Firestore/core/src/firebase/firestore/local/leveldb_mutation_queue.mm @@ -179,6 +179,8 @@ BatchId LoadNextBatchIdFromDb(DB* db) { for (FSTMutation* mutation : [batch mutations]) { key = LevelDbDocumentMutationKey::Key(user_id_, mutation.key, batch_id); db_.currentTransaction->Put(key, empty_buffer); + + db_.indexManager->AddToCollectionParentIndex(mutation.key.path().PopLast()); } return batch; @@ -270,6 +272,9 @@ BatchId LoadNextBatchIdFromDb(DB* db) { LevelDbMutationQueue::AllMutationBatchesAffectingQuery(FSTQuery* query) { HARD_ASSERT(![query isDocumentQuery], "Document queries shouldn't go down this path"); + HARD_ASSERT( + ![query isCollectionGroupQuery], + "CollectionGroup queries should be handled in LocalDocumentsView"); const ResourcePath& query_path = query.path; size_t immediate_children_path_length = query_path.size() + 1; diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_query_cache.h b/Firestore/core/src/firebase/firestore/local/leveldb_query_cache.h index 6dae8bb7974..caf28c403cf 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_query_cache.h +++ b/Firestore/core/src/firebase/firestore/local/leveldb_query_cache.h @@ -47,11 +47,6 @@ namespace local { /** Cached Queries backed by LevelDB. */ class LevelDbQueryCache : public QueryCache { public: - /** Enumerator callback type for orphaned documents */ - typedef void (^OrphanedDocumentEnumerator)(const model::DocumentKey&, - model::ListenSequenceNumber, - BOOL*); - /** * Retrieves the global singleton metadata row from the given database, if it * exists. @@ -75,7 +70,7 @@ class LevelDbQueryCache : public QueryCache { FSTQueryData* _Nullable GetTarget(FSTQuery* query) override; - void EnumerateTargets(TargetEnumerator block) override; + void EnumerateTargets(const TargetCallback& callback) override; int RemoveTargets(model::ListenSequenceNumber upper_bound, const std::unordered_map& @@ -123,7 +118,7 @@ class LevelDbQueryCache : public QueryCache { // Non-interface methods void Start(); - void EnumerateOrphanedDocuments(OrphanedDocumentEnumerator block); + void EnumerateOrphanedDocuments(const OrphanedDocumentCallback& callback); private: void Save(FSTQueryData* query_data); diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_query_cache.mm b/Firestore/core/src/firebase/firestore/local/leveldb_query_cache.mm index a9e14635bac..4412c466bfb 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_query_cache.mm +++ b/Firestore/core/src/firebase/firestore/local/leveldb_query_cache.mm @@ -170,16 +170,15 @@ return nil; } -void LevelDbQueryCache::EnumerateTargets(TargetEnumerator block) { +void LevelDbQueryCache::EnumerateTargets(const TargetCallback& callback) { // Enumerate all targets, give their sequence numbers. std::string target_prefix = LevelDbTargetKey::KeyPrefix(); auto it = db_.currentTransaction->NewIterator(); it->Seek(target_prefix); - BOOL stop = NO; - for (; !stop && it->Valid() && absl::StartsWith(it->key(), target_prefix); + for (; it->Valid() && absl::StartsWith(it->key(), target_prefix); it->Next()) { FSTQueryData* target = DecodeTarget(it->value()); - block(target, &stop); + callback(target); } } @@ -306,23 +305,22 @@ } void LevelDbQueryCache::EnumerateOrphanedDocuments( - OrphanedDocumentEnumerator block) { + const OrphanedDocumentCallback& callback) { std::string document_target_prefix = LevelDbDocumentTargetKey::KeyPrefix(); auto it = db_.currentTransaction->NewIterator(); it->Seek(document_target_prefix); ListenSequenceNumber next_to_report = 0; DocumentKey key_to_report; LevelDbDocumentTargetKey key; - BOOL stop = NO; - for (; !stop && it->Valid() && - absl::StartsWith(it->key(), document_target_prefix); + + for (; it->Valid() && absl::StartsWith(it->key(), document_target_prefix); it->Next()) { HARD_ASSERT(key.Decode(it->key()), "Failed to decode DocumentTarget key"); if (key.IsSentinel()) { // if next_to_report is non-zero, report it, this is a new key so the last // one must be not be a member of any targets. if (next_to_report != 0) { - block(key_to_report, next_to_report, &stop); + callback(key_to_report, next_to_report); } // set next_to_report to be this sequence number. It's the next one we // might report, if we don't find any targets for this document. @@ -335,10 +333,10 @@ next_to_report = 0; } } - // if not stop and next_to_report is non-zero, report it. We didn't find any - // targets for that document, and we weren't asked to stop. - if (!stop && next_to_report != 0) { - block(key_to_report, next_to_report, &stop); + // if next_to_report is non-zero, report it. We didn't find any targets for + // that document, and we weren't asked to stop. + if (next_to_report != 0) { + callback(key_to_report, next_to_report); } } diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h b/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h index 536d74c1d33..99bf754b87a 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h +++ b/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h @@ -57,7 +57,8 @@ class LevelDbRemoteDocumentCache : public RemoteDocumentCache { FSTMaybeDocument* DecodeMaybeDocument(absl::string_view encoded, const model::DocumentKey& key); - FSTLevelDB* db_; + // This instance is owned by FSTLevelDB; avoid a retain cycle. + __weak FSTLevelDB* db_; FSTLocalSerializer* serializer_; }; diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.mm b/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.mm index 07bb1205c59..69002e03795 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.mm +++ b/Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.mm @@ -47,6 +47,8 @@ std::string ldb_key = LevelDbRemoteDocumentKey::Key(document.key); db_.currentTransaction->Put(ldb_key, [serializer_ encodedMaybeDocument:document]); + + db_.indexManager->AddToCollectionParentIndex(document.key.path().PopLast()); } void LevelDbRemoteDocumentCache::Remove(const DocumentKey& key) { @@ -90,6 +92,10 @@ } DocumentMap LevelDbRemoteDocumentCache::GetMatching(FSTQuery* query) { + HARD_ASSERT( + ![query isCollectionGroupQuery], + "CollectionGroup queries should be handled in LocalDocumentsView"); + DocumentMap results; // Use the query path as a prefix for testing if a document matches the query. diff --git a/Firestore/core/src/firebase/firestore/local/local_documents_view.h b/Firestore/core/src/firebase/firestore/local/local_documents_view.h new file mode 100644 index 00000000000..b4dd6501385 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/local_documents_view.h @@ -0,0 +1,117 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LOCAL_DOCUMENTS_VIEW_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LOCAL_DOCUMENTS_VIEW_H_ + +#import + +#include + +#include "Firestore/core/src/firebase/firestore/local/index_manager.h" +#include "Firestore/core/src/firebase/firestore/local/mutation_queue.h" +#include "Firestore/core/src/firebase/firestore/local/remote_document_cache.h" +#include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/document_map.h" + +NS_ASSUME_NONNULL_BEGIN + +@class FSTMaybeDocument; +@class FSTMutationBatch; +@class FSTQuery; + +namespace firebase { +namespace firestore { +namespace local { + +/** + * A readonly view of the local state of all documents we're tracking (i.e. we + * have a cached version in remoteDocumentCache or local mutations for the + * document). The view is computed by applying the mutations in the + * FSTMutationQueue to the FSTRemoteDocumentCache. + */ +class LocalDocumentsView { + public: + LocalDocumentsView(RemoteDocumentCache* remote_document_cache, + MutationQueue* mutation_queue, + IndexManager* index_manager) + : remote_document_cache_{remote_document_cache}, + mutation_queue_{mutation_queue}, + index_manager_{index_manager} { + } + + /** + * Gets the local view of the document identified by `key`. + * + * @return Local view of the document or nil if we don't have any cached state + * for it. + */ + FSTMaybeDocument* _Nullable GetDocument(const model::DocumentKey& key); + + /** + * Gets the local view of the documents identified by `keys`. + * + * If we don't have cached state for a document in `keys`, a + * FSTDeletedDocument will be stored for that key in the resulting set. + */ + model::MaybeDocumentMap GetDocuments(const model::DocumentKeySet& keys); + + /** + * Similar to `documentsForKeys`, but creates the local view from the given + * `baseDocs` without retrieving documents from the local store. + */ + model::MaybeDocumentMap GetLocalViewOfDocuments( + const model::MaybeDocumentMap& base_docs); + + /** Performs a query against the local view of all documents. */ + model::DocumentMap GetDocumentsMatchingQuery(FSTQuery* query); + + private: + /** Internal version of GetDocument that allows re-using batches. */ + FSTMaybeDocument* _Nullable GetDocument( + const model::DocumentKey& key, + const std::vector& batches); + + /** + * Returns the view of the given `docs` as they would appear after applying + * all mutations in the given `batches`. + */ + model::MaybeDocumentMap ApplyLocalMutationsToDocuments( + const model::MaybeDocumentMap& docs, + const std::vector& batches); + + /** Performs a simple document lookup for the given path. */ + model::DocumentMap GetDocumentsMatchingDocumentQuery( + const model::ResourcePath& doc_path); + + model::DocumentMap GetDocumentsMatchingCollectionGroupQuery(FSTQuery* query); + + /** Queries the remote documents and overlays mutations. */ + model::DocumentMap GetDocumentsMatchingCollectionQuery(FSTQuery* query); + + RemoteDocumentCache* remote_document_cache_; + MutationQueue* mutation_queue_; + IndexManager* index_manager_; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LOCAL_DOCUMENTS_VIEW_H_ diff --git a/Firestore/core/src/firebase/firestore/local/local_documents_view.mm b/Firestore/core/src/firebase/firestore/local/local_documents_view.mm new file mode 100644 index 00000000000..c27692b231b --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/local_documents_view.mm @@ -0,0 +1,222 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "Firestore/core/src/firebase/firestore/local/local_documents_view.h" + +#include + +#import "Firestore/Source/Core/FSTQuery.h" +#import "Firestore/Source/Model/FSTDocument.h" +#import "Firestore/Source/Model/FSTMutation.h" +#import "Firestore/Source/Model/FSTMutationBatch.h" + +#include "Firestore/core/src/firebase/firestore/local/mutation_queue.h" +#include "Firestore/core/src/firebase/firestore/local/remote_document_cache.h" +#include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_map.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace local { + +using model::DocumentKey; +using model::DocumentKeySet; +using model::DocumentMap; +using model::MaybeDocumentMap; +using model::ResourcePath; +using model::SnapshotVersion; +using util::MakeString; + +FSTMaybeDocument* _Nullable LocalDocumentsView::GetDocument( + const DocumentKey& key) { + std::vector batches = + mutation_queue_->AllMutationBatchesAffectingDocumentKey(key); + return GetDocument(key, batches); +} + +FSTMaybeDocument* _Nullable LocalDocumentsView::GetDocument( + const DocumentKey& key, const std::vector& batches) { + FSTMaybeDocument* _Nullable document = remote_document_cache_->Get(key); + for (FSTMutationBatch* batch : batches) { + document = [batch applyToLocalDocument:document documentKey:key]; + } + + return document; +} + +MaybeDocumentMap LocalDocumentsView::ApplyLocalMutationsToDocuments( + const MaybeDocumentMap& docs, + const std::vector& batches) { + MaybeDocumentMap results; + + for (const auto& kv : docs) { + const DocumentKey& key = kv.first; + FSTMaybeDocument* local_view = kv.second; + for (FSTMutationBatch* batch : batches) { + local_view = [batch applyToLocalDocument:local_view documentKey:key]; + } + results = results.insert(key, local_view); + } + return results; +} + +MaybeDocumentMap LocalDocumentsView::GetDocuments(const DocumentKeySet& keys) { + MaybeDocumentMap docs = remote_document_cache_->GetAll(keys); + return GetLocalViewOfDocuments(docs); +} + +/** + * Similar to `documentsForKeys`, but creates the local view from the given + * `baseDocs` without retrieving documents from the local store. + */ +MaybeDocumentMap LocalDocumentsView::GetLocalViewOfDocuments( + const MaybeDocumentMap& base_docs) { + MaybeDocumentMap results; + + DocumentKeySet all_keys; + for (const auto& kv : base_docs) { + all_keys = all_keys.insert(kv.first); + } + std::vector batches = + mutation_queue_->AllMutationBatchesAffectingDocumentKeys(all_keys); + + MaybeDocumentMap docs = ApplyLocalMutationsToDocuments(base_docs, batches); + + for (const auto& kv : docs) { + const DocumentKey& key = kv.first; + FSTMaybeDocument* maybe_doc = kv.second; + + // TODO(http://b/32275378): Don't conflate missing / deleted. + if (!maybe_doc) { + maybe_doc = [FSTDeletedDocument documentWithKey:key + version:SnapshotVersion::None() + hasCommittedMutations:NO]; + } + results = results.insert(key, maybe_doc); + } + + return results; +} + +DocumentMap LocalDocumentsView::GetDocumentsMatchingQuery(FSTQuery* query) { + if ([query isDocumentQuery]) { + return GetDocumentsMatchingDocumentQuery(query.path); + } else if ([query isCollectionGroupQuery]) { + return GetDocumentsMatchingCollectionGroupQuery(query); + } else { + return GetDocumentsMatchingCollectionQuery(query); + } +} + +DocumentMap LocalDocumentsView::GetDocumentsMatchingDocumentQuery( + const ResourcePath& doc_path) { + DocumentMap result; + // Just do a simple document lookup. + FSTMaybeDocument* doc = GetDocument(DocumentKey{doc_path}); + if ([doc isKindOfClass:[FSTDocument class]]) { + result = result.insert(doc.key, static_cast(doc)); + } + return result; +} + +model::DocumentMap LocalDocumentsView::GetDocumentsMatchingCollectionGroupQuery( + FSTQuery* query) { + HARD_ASSERT( + query.path.empty(), + "Currently we only support collection group queries at the root."); + + std::string collection_id = MakeString(query.collectionGroup); + std::vector parents = + index_manager_->GetCollectionParents(collection_id); + DocumentMap results; + + // Perform a collection query against each parent that contains the + // collection_id and aggregate the results. + for (const ResourcePath& parent : parents) { + FSTQuery* collection_query = + [query collectionQueryAtPath:parent.Append(collection_id)]; + DocumentMap collection_results = + GetDocumentsMatchingCollectionQuery(collection_query); + for (const auto& kv : collection_results.underlying_map()) { + const DocumentKey& key = kv.first; + FSTDocument* doc = static_cast(kv.second); + results = results.insert(key, doc); + } + } + return results; +} + +DocumentMap LocalDocumentsView::GetDocumentsMatchingCollectionQuery( + FSTQuery* query) { + DocumentMap results = remote_document_cache_->GetMatching(query); + // Get locally persisted mutation batches. + std::vector matchingBatches = + mutation_queue_->AllMutationBatchesAffectingQuery(query); + + for (FSTMutationBatch* batch : matchingBatches) { + for (FSTMutation* mutation : [batch mutations]) { + // Only process documents belonging to the collection. + if (!query.path.IsImmediateParentOf(mutation.key.path())) { + continue; + } + + const DocumentKey& key = mutation.key; + // base_doc may be nil for the documents that weren't yet written to the + // backend. + FSTMaybeDocument* base_doc = nil; + auto found = results.underlying_map().find(key); + if (found != results.underlying_map().end()) { + base_doc = found->second; + } + FSTMaybeDocument* mutated_doc = + [mutation applyToLocalDocument:base_doc + baseDocument:base_doc + localWriteTime:batch.localWriteTime]; + + if ([mutated_doc isKindOfClass:[FSTDocument class]]) { + results = results.insert(key, static_cast(mutated_doc)); + } else { + results = results.erase(key); + } + } + } + + // Finally, filter out any documents that don't actually match the query. Note + // that the extra reference here prevents DocumentMap's destructor from + // deallocating the initial unfiltered results while we're iterating over + // them. + DocumentMap unfiltered = results; + for (const auto& kv : unfiltered.underlying_map()) { + const DocumentKey& key = kv.first; + auto* doc = static_cast(kv.second); + if (![query matchesDocument:doc]) { + results = results.erase(key); + } + } + + return results; +} + +} // namespace local +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END diff --git a/Firestore/core/src/firebase/firestore/local/local_serializer.cc b/Firestore/core/src/firebase/firestore/local/local_serializer.cc index 49d25968f4f..2b7944f09b3 100644 --- a/Firestore/core/src/firebase/firestore/local/local_serializer.cc +++ b/Firestore/core/src/firebase/firestore/local/local_serializer.cc @@ -29,6 +29,7 @@ #include "Firestore/core/src/firebase/firestore/model/no_document.h" #include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "Firestore/core/src/firebase/firestore/model/unknown_document.h" +#include "Firestore/core/src/firebase/firestore/nanopb/nanopb_util.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/string_format.h" @@ -42,9 +43,9 @@ using model::MaybeDocument; using model::Mutation; using model::MutationBatch; using model::NoDocument; -using model::ObjectValue; using model::SnapshotVersion; using model::UnknownDocument; +using nanopb::CheckedSize; using nanopb::Reader; using nanopb::Writer; using remote::MakeArray; @@ -124,13 +125,11 @@ google_firestore_v1_Document LocalSerializer::EncodeDocument( rpc_serializer_.EncodeString(rpc_serializer_.EncodeKey(doc.key())); // Encode Document.fields (unless it's empty) - size_t count = doc.data().object_value().internal_value.size(); - HARD_ASSERT(count <= std::numeric_limits::max(), - "Unable to encode specified document. Too many fields."); - result.fields_count = static_cast(count); + pb_size_t count = CheckedSize(doc.data().GetInternalValue().size()); + result.fields_count = count; result.fields = MakeArray(count); int i = 0; - for (const auto& kv : doc.data().object_value().internal_value) { + for (const auto& kv : doc.data().GetInternalValue()) { result.fields[i].key = rpc_serializer_.EncodeString(kv.first); result.fields[i].value = rpc_serializer_.EncodeFieldValue(kv.second); i++; @@ -258,10 +257,8 @@ firestore_client_WriteBatch LocalSerializer::EncodeMutationBatch( firestore_client_WriteBatch result{}; result.batch_id = mutation_batch.batch_id(); - size_t count = mutation_batch.mutations().size(); - HARD_ASSERT(count <= std::numeric_limits::max(), - "Unable to encode specified mutation batch. Too many mutations."); - result.writes_count = static_cast(count); + pb_size_t count = CheckedSize(mutation_batch.mutations().size()); + result.writes_count = count; result.writes = MakeArray(count); int i = 0; for (const std::unique_ptr& mutation : mutation_batch.mutations()) { diff --git a/Firestore/core/src/firebase/firestore/local/memory_index_manager.cc b/Firestore/core/src/firebase/firestore/local/memory_index_manager.cc new file mode 100644 index 00000000000..08274167c3c --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/memory_index_manager.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/local/memory_index_manager.h" + +#include +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" + +namespace firebase { +namespace firestore { +namespace local { + +using model::ResourcePath; + +bool MemoryCollectionParentIndex::Add(const ResourcePath& collection_path) { + HARD_ASSERT(collection_path.size() % 2 == 1, "Expected a collection path."); + + std::string collection_id = collection_path.last_segment(); + ResourcePath parent_path = collection_path.PopLast(); + std::set& existingParents = index_[collection_id]; + bool inserted = existingParents.insert(parent_path).second; + return inserted; +} + +std::vector MemoryCollectionParentIndex::GetEntries( + const std::string& collection_id) const { + std::vector result; + auto found = index_.find(collection_id); + if (found != index_.end()) { + const std::set& parent_paths = found->second; + std::copy(parent_paths.begin(), parent_paths.end(), + std::back_inserter(result)); + } + return result; +} + +void MemoryIndexManager::AddToCollectionParentIndex( + const ResourcePath& collection_path) { + collection_parents_index_.Add(collection_path); +} + +std::vector MemoryIndexManager::GetCollectionParents( + const std::string& collection_id) { + return collection_parents_index_.GetEntries(collection_id); +} + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/local/memory_index_manager.h b/Firestore/core/src/firebase/firestore/local/memory_index_manager.h new file mode 100644 index 00000000000..06a0eb83ba1 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/memory_index_manager.h @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_MEMORY_INDEX_MANAGER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_MEMORY_INDEX_MANAGER_H_ + +#include +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/local/index_manager.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" + +namespace firebase { +namespace firestore { +namespace local { + +/** + * Internal implementation of the collection-parent index. Also used for + * in-memory caching by LevelDbIndexManager and initial index population during + * schema migration. + */ +class MemoryCollectionParentIndex { + public: + // Returns false if the entry already existed. + bool Add(const model::ResourcePath& collection_path); + + std::vector GetEntries( + const std::string& collection_id) const; + + private: + std::unordered_map> index_; +}; + +/** An in-memory implementation of IndexManager. */ +class MemoryIndexManager : public IndexManager { + public: + void AddToCollectionParentIndex( + const model::ResourcePath& collection_path) override; + + std::vector GetCollectionParents( + const std::string& collection_id) override; + + private: + MemoryCollectionParentIndex collection_parents_index_; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_MEMORY_INDEX_MANAGER_H_ diff --git a/Firestore/core/src/firebase/firestore/local/memory_mutation_queue.h b/Firestore/core/src/firebase/firestore/local/memory_mutation_queue.h index 463439e9331..927317cc3df 100644 --- a/Firestore/core/src/firebase/firestore/local/memory_mutation_queue.h +++ b/Firestore/core/src/firebase/firestore/local/memory_mutation_queue.h @@ -29,7 +29,7 @@ #import "Firestore/Source/Public/FIRTimestamp.h" #include "Firestore/core/src/firebase/firestore/immutable/sorted_set.h" -#include "Firestore/core/src/firebase/firestore/local/document_reference.h" +#include "Firestore/core/src/firebase/firestore/local/document_key_reference.h" #include "Firestore/core/src/firebase/firestore/local/mutation_queue.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/document_key_set.h" @@ -94,8 +94,8 @@ class MemoryMutationQueue : public MutationQueue { void SetLastStreamToken(NSData* _Nullable token) override; private: - using DocumentReferenceSet = - immutable::SortedSet; + using DocumentKeyReferenceSet = + immutable::SortedSet; std::vector AllMutationBatchesWithIds( const std::set& batch_ids); @@ -111,7 +111,8 @@ class MemoryMutationQueue : public MutationQueue { */ int IndexOfBatchId(model::BatchId batch_id); - FSTMemoryPersistence* persistence_; + // This instance is owned by FSTMemoryPersistence; avoid a retain cycle. + __weak FSTMemoryPersistence* persistence_; /** * A FIFO queue of all mutations to apply to the backend. Mutations are added * to the end of the queue as they're written, and removed from the front of @@ -146,7 +147,7 @@ class MemoryMutationQueue : public MutationQueue { NSData* _Nullable last_stream_token_; /** An ordered mapping between documents and the mutation batch IDs. */ - DocumentReferenceSet batches_by_document_key_; + DocumentKeyReferenceSet batches_by_document_key_; }; } // namespace local diff --git a/Firestore/core/src/firebase/firestore/local/memory_mutation_queue.mm b/Firestore/core/src/firebase/firestore/local/memory_mutation_queue.mm index c17dfd2c90c..aace5e5ce3c 100644 --- a/Firestore/core/src/firebase/firestore/local/memory_mutation_queue.mm +++ b/Firestore/core/src/firebase/firestore/local/memory_mutation_queue.mm @@ -25,7 +25,7 @@ #import "Firestore/Source/Model/FSTMutation.h" #import "Firestore/Source/Model/FSTMutationBatch.h" -#include "Firestore/core/src/firebase/firestore/local/document_reference.h" +#include "Firestore/core/src/firebase/firestore/local/document_key_reference.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" @@ -95,10 +95,13 @@ mutations:std::move(mutations)]; queue_.push_back(batch); - // Track references by document key. + // Track references by document key and index collection parents. for (FSTMutation* mutation : [batch mutations]) { batches_by_document_key_ = batches_by_document_key_.insert( - DocumentReference{mutation.key, batch_id}); + DocumentKeyReference{mutation.key, batch_id}); + + persistence_.indexManager->AddToCollectionParentIndex( + mutation.key.path().PopLast()); } return batch; @@ -118,7 +121,7 @@ const DocumentKey& key = mutation.key; [persistence_.referenceDelegate removeMutationReference:key]; - DocumentReference reference{key, batch.batchID}; + DocumentKeyReference reference{key, batch.batchID}; batches_by_document_key_ = batches_by_document_key_.erase(reference); } } @@ -129,7 +132,7 @@ // First find the set of affected batch IDs. std::set batch_ids; for (const DocumentKey& key : document_keys) { - DocumentReference start{key, 0}; + DocumentKeyReference start{key, 0}; for (const auto& reference : batches_by_document_key_.values_from(start)) { if (key != reference.key()) break; @@ -146,7 +149,7 @@ const DocumentKey& key) { std::vector result; - DocumentReference start{key, 0}; + DocumentKeyReference start{key, 0}; for (const auto& reference : batches_by_document_key_.values_from(start)) { if (key != reference.key()) break; @@ -159,6 +162,10 @@ std::vector MemoryMutationQueue::AllMutationBatchesAffectingQuery(FSTQuery* query) { + HARD_ASSERT( + ![query isCollectionGroupQuery], + "CollectionGroup queries should be handled in LocalDocumentsView"); + // Use the query path as a prefix for testing if a document matches the query. const ResourcePath& prefix = query.path; size_t immediate_children_path_length = prefix.size() + 1; @@ -171,7 +178,7 @@ if (!DocumentKey::IsDocumentKey(start_path)) { start_path = start_path.Append(""); } - DocumentReference start{DocumentKey{start_path}, 0}; + DocumentKeyReference start{DocumentKey{start_path}, 0}; // Find unique batchIDs referenced by all documents potentially matching the // query. @@ -235,7 +242,7 @@ bool MemoryMutationQueue::ContainsKey(const model::DocumentKey& key) { // Create a reference with a zero ID as the start position to find any // document reference with this key. - DocumentReference reference{key, 0}; + DocumentKeyReference reference{key, 0}; auto range = batches_by_document_key_.values_from(reference); auto begin = range.begin(); return begin != range.end() && begin->key() == key; diff --git a/Firestore/core/src/firebase/firestore/local/memory_query_cache.h b/Firestore/core/src/firebase/firestore/local/memory_query_cache.h index ad11b693e85..747756a6c9f 100644 --- a/Firestore/core/src/firebase/firestore/local/memory_query_cache.h +++ b/Firestore/core/src/firebase/firestore/local/memory_query_cache.h @@ -32,6 +32,7 @@ #include "Firestore/core/src/firebase/firestore/model/document_key_set.h" #include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "Firestore/core/src/firebase/firestore/model/types.h" +#include "Firestore/core/src/firebase/firestore/util/objc_compatibility.h" @class FSTLocalSerializer; @class FSTMemoryPersistence; @@ -57,7 +58,7 @@ class MemoryQueryCache : public QueryCache { FSTQueryData* _Nullable GetTarget(FSTQuery* query) override; - void EnumerateTargets(TargetEnumerator block) override; + void EnumerateTargets(const TargetCallback& callback) override; int RemoveTargets(model::ListenSequenceNumber upper_bound, const std::unordered_map& @@ -78,7 +79,7 @@ class MemoryQueryCache : public QueryCache { size_t CalculateByteSize(FSTLocalSerializer* serializer); size_t size() const override { - return [queries_ count]; + return queries_.size(); } model::ListenSequenceNumber highest_listen_sequence_number() const override { @@ -94,7 +95,9 @@ class MemoryQueryCache : public QueryCache { void SetLastRemoteSnapshotVersion(model::SnapshotVersion version) override; private: - FSTMemoryPersistence* persistence_; + // This instance is owned by FSTMemoryPersistence; avoid a retain cycle. + __weak FSTMemoryPersistence* persistence_; + /** The highest sequence number encountered */ model::ListenSequenceNumber highest_listen_sequence_number_; /** The highest numbered target ID encountered. */ @@ -103,9 +106,12 @@ class MemoryQueryCache : public QueryCache { model::SnapshotVersion last_remote_snapshot_version_; /** Maps a query to the data about that query. */ - NSMutableDictionary* queries_; - /** A ordered bidirectional mapping between documents and the remote target - * IDs. */ + util::objc::unordered_map queries_; + + /** + * A ordered bidirectional mapping between documents and the remote target + * IDs. + */ ReferenceSet references_; }; diff --git a/Firestore/core/src/firebase/firestore/local/memory_query_cache.mm b/Firestore/core/src/firebase/firestore/local/memory_query_cache.mm index 9c76b36e664..05a69858d41 100644 --- a/Firestore/core/src/firebase/firestore/local/memory_query_cache.mm +++ b/Firestore/core/src/firebase/firestore/local/memory_query_cache.mm @@ -15,12 +15,16 @@ */ #include "Firestore/core/src/firebase/firestore/local/memory_query_cache.h" + #import +#include + #import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h" #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Local/FSTMemoryPersistence.h" #import "Firestore/Source/Local/FSTQueryData.h" + #include "Firestore/core/src/firebase/firestore/model/document_key.h" using firebase::firestore::model::DocumentKey; @@ -40,7 +44,7 @@ highest_listen_sequence_number_(ListenSequenceNumber(0)), highest_target_id_(TargetId(0)), last_remote_snapshot_version_(SnapshotVersion::None()), - queries_([NSMutableDictionary dictionary]) { + queries_() { } void MemoryQueryCache::AddTarget(FSTQueryData* query_data) { @@ -59,36 +63,41 @@ } void MemoryQueryCache::RemoveTarget(FSTQueryData* query_data) { - [queries_ removeObjectForKey:query_data.query]; + queries_.erase(query_data.query); references_.RemoveReferences(query_data.targetID); } FSTQueryData* _Nullable MemoryQueryCache::GetTarget(FSTQuery* query) { - return queries_[query]; + auto iter = queries_.find(query); + return iter == queries_.end() ? nil : iter->second; } -void MemoryQueryCache::EnumerateTargets(TargetEnumerator block) { - [queries_ enumerateKeysAndObjectsUsingBlock:^( - FSTQuery* query, FSTQueryData* query_data, BOOL* stop) { - block(query_data, stop); - }]; +void MemoryQueryCache::EnumerateTargets(const TargetCallback& callback) { + for (const auto& kv : queries_) { + callback(kv.second); + } } int MemoryQueryCache::RemoveTargets( model::ListenSequenceNumber upper_bound, const std::unordered_map& live_targets) { - NSMutableArray* toRemove = [NSMutableArray array]; - [queries_ enumerateKeysAndObjectsUsingBlock:^( - FSTQuery* query, FSTQueryData* queryData, BOOL* stop) { - if (queryData.sequenceNumber <= upper_bound) { - if (live_targets.find(queryData.targetID) == live_targets.end()) { - [toRemove addObject:query]; - references_.RemoveReferences(queryData.targetID); + std::vector to_remove; + for (const auto& kv : queries_) { + FSTQuery* query = kv.first; + FSTQueryData* query_data = kv.second; + + if (query_data.sequenceNumber <= upper_bound) { + if (live_targets.find(query_data.targetID) == live_targets.end()) { + to_remove.push_back(query); + references_.RemoveReferences(query_data.targetID); } } - }]; - [queries_ removeObjectsForKeys:toRemove]; - return static_cast([toRemove count]); + } + + for (FSTQuery* element : to_remove) { + queries_.erase(element); + } + return static_cast(to_remove.size()); } void MemoryQueryCache::AddMatchingKeys(const DocumentKeySet& keys, @@ -116,11 +125,11 @@ } size_t MemoryQueryCache::CalculateByteSize(FSTLocalSerializer* serializer) { - __block size_t count = 0; - [queries_ enumerateKeysAndObjectsUsingBlock:^( - FSTQuery* query, FSTQueryData* query_data, BOOL* stop) { + size_t count = 0; + for (const auto& kv : queries_) { + FSTQueryData* query_data = kv.second; count += [[serializer encodedQueryData:query_data] serializedSize]; - }]; + } return count; } diff --git a/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.h b/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.h index d1eebd08e99..e0f3cda628a 100644 --- a/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.h +++ b/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.h @@ -32,6 +32,7 @@ @class FSTLocalSerializer; @class FSTMaybeDocument; @class FSTMemoryLRUReferenceDelegate; +@class FSTMemoryPersistence; @class FSTQuery; NS_ASSUME_NONNULL_BEGIN @@ -42,6 +43,8 @@ namespace local { class MemoryRemoteDocumentCache : public RemoteDocumentCache { public: + explicit MemoryRemoteDocumentCache(FSTMemoryPersistence *persistence); + void Add(FSTMaybeDocument *document) override; void Remove(const model::DocumentKey &key) override; @@ -58,6 +61,9 @@ class MemoryRemoteDocumentCache : public RemoteDocumentCache { private: /** Underlying cache of documents. */ model::MaybeDocumentMap docs_; + + // This instance is owned by FSTMemoryPersistence; avoid a retain cycle. + __weak FSTMemoryPersistence *persistence_; }; } // namespace local diff --git a/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.mm b/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.mm index 2514eaaa16d..82503a0f604 100644 --- a/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.mm +++ b/Firestore/core/src/firebase/firestore/local/memory_remote_document_cache.mm @@ -20,6 +20,8 @@ #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Local/FSTMemoryPersistence.h" +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" + using firebase::firestore::model::DocumentKey; using firebase::firestore::model::DocumentKeySet; using firebase::firestore::model::DocumentMap; @@ -45,8 +47,16 @@ size_t DocumentKeyByteSize(const DocumentKey& key) { } } // namespace +MemoryRemoteDocumentCache::MemoryRemoteDocumentCache( + FSTMemoryPersistence* persistence) { + persistence_ = persistence; +} + void MemoryRemoteDocumentCache::Add(FSTMaybeDocument* document) { docs_ = docs_.insert(document.key, document); + + persistence_.indexManager->AddToCollectionParentIndex( + document.key.path().PopLast()); } void MemoryRemoteDocumentCache::Remove(const DocumentKey& key) { @@ -71,6 +81,10 @@ size_t DocumentKeyByteSize(const DocumentKey& key) { } DocumentMap MemoryRemoteDocumentCache::GetMatching(FSTQuery* query) { + HARD_ASSERT( + ![query isCollectionGroupQuery], + "CollectionGroup queries should be handled in LocalDocumentsView"); + DocumentMap results; // Documents are ordered by key, so we can use a prefix scan to narrow down @@ -122,4 +136,4 @@ size_t DocumentKeyByteSize(const DocumentKey& key) { } // namespace local } // namespace firestore -} // namespace firebase \ No newline at end of file +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/local/query_cache.h b/Firestore/core/src/firebase/firestore/local/query_cache.h index 9fa61c1b519..75240ceb9c1 100644 --- a/Firestore/core/src/firebase/firestore/local/query_cache.h +++ b/Firestore/core/src/firebase/firestore/local/query_cache.h @@ -23,6 +23,7 @@ #import +#include #include #include "Firestore/core/src/firebase/firestore/model/document_key.h" @@ -39,6 +40,11 @@ namespace firebase { namespace firestore { namespace local { +using OrphanedDocumentCallback = + std::function; + +using TargetCallback = std::function; + /** * Represents cached targets received from the remote backend. This contains * both a mapping between targets and the documents that matched them according @@ -49,8 +55,6 @@ namespace local { */ class QueryCache { public: - typedef void (^TargetEnumerator)(FSTQueryData*, BOOL*); - virtual ~QueryCache() { } @@ -89,7 +93,7 @@ class QueryCache { */ virtual FSTQueryData* _Nullable GetTarget(FSTQuery* query) = 0; - virtual void EnumerateTargets(TargetEnumerator block) = 0; + virtual void EnumerateTargets(const TargetCallback& callback) = 0; virtual int RemoveTargets( model::ListenSequenceNumber upper_bound, diff --git a/Firestore/core/src/firebase/firestore/local/reference_set.cc b/Firestore/core/src/firebase/firestore/local/reference_set.cc index 9529b1ffed7..57431d75c9f 100644 --- a/Firestore/core/src/firebase/firestore/local/reference_set.cc +++ b/Firestore/core/src/firebase/firestore/local/reference_set.cc @@ -15,8 +15,9 @@ */ #include "Firestore/core/src/firebase/firestore/local/reference_set.h" + #include "Firestore/core/src/firebase/firestore/immutable/sorted_set.h" -#include "Firestore/core/src/firebase/firestore/local/document_reference.h" +#include "Firestore/core/src/firebase/firestore/local/document_key_reference.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" namespace firebase { @@ -27,7 +28,7 @@ using model::DocumentKey; using model::DocumentKeySet; void ReferenceSet::AddReference(const DocumentKey& key, int id) { - DocumentReference reference{key, id}; + DocumentKeyReference reference{key, id}; by_key_ = by_key_.insert(reference); by_id_ = by_id_.insert(reference); } @@ -39,7 +40,7 @@ void ReferenceSet::AddReferences(const DocumentKeySet& keys, int id) { } void ReferenceSet::RemoveReference(const DocumentKey& key, int id) { - RemoveReference(DocumentReference{key, id}); + RemoveReference(DocumentKeyReference{key, id}); } void ReferenceSet::RemoveReferences( @@ -50,8 +51,8 @@ void ReferenceSet::RemoveReferences( } DocumentKeySet ReferenceSet::RemoveReferences(int id) { - DocumentReference start{DocumentKey::Empty(), id}; - DocumentReference end{DocumentKey::Empty(), id + 1}; + DocumentKeyReference start{DocumentKey::Empty(), id}; + DocumentKeyReference end{DocumentKey::Empty(), id + 1}; DocumentKeySet removed{}; @@ -65,19 +66,19 @@ DocumentKeySet ReferenceSet::RemoveReferences(int id) { void ReferenceSet::RemoveAllReferences() { auto initial = by_key_; - for (const DocumentReference& reference : initial) { + for (const DocumentKeyReference& reference : initial) { RemoveReference(reference); } } -void ReferenceSet::RemoveReference(const DocumentReference& reference) { +void ReferenceSet::RemoveReference(const DocumentKeyReference& reference) { by_key_ = by_key_.erase(reference); by_id_ = by_id_.erase(reference); } DocumentKeySet ReferenceSet::ReferencedKeys(int id) { - DocumentReference start{DocumentKey::Empty(), id}; - DocumentReference end{DocumentKey::Empty(), id + 1}; + DocumentKeyReference start{DocumentKey::Empty(), id}; + DocumentKeyReference end{DocumentKey::Empty(), id + 1}; DocumentKeySet keys; for (const auto& reference : by_id_.values_in(start, end)) { @@ -89,7 +90,7 @@ DocumentKeySet ReferenceSet::ReferencedKeys(int id) { bool ReferenceSet::ContainsKey(const DocumentKey& key) { // Create a reference with a zero ID as the start position to find any // document reference with this key. - DocumentReference start{key, 0}; + DocumentKeyReference start{key, 0}; auto range = by_key_.values_from(start); auto begin = range.begin(); diff --git a/Firestore/core/src/firebase/firestore/local/reference_set.h b/Firestore/core/src/firebase/firestore/local/reference_set.h index 58677e200ff..0e14c998bbd 100644 --- a/Firestore/core/src/firebase/firestore/local/reference_set.h +++ b/Firestore/core/src/firebase/firestore/local/reference_set.h @@ -18,7 +18,7 @@ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_REFERENCE_SET_H_ #include "Firestore/core/src/firebase/firestore/immutable/sorted_set.h" -#include "Firestore/core/src/firebase/firestore/local/document_reference.h" +#include "Firestore/core/src/firebase/firestore/local/document_key_reference.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/document_key_set.h" @@ -31,7 +31,7 @@ namespace local { * (either a TargetId or BatchId). As references are added to or removed from * the set corresponding events are emitted to a registered garbage collector. * - * Each reference is represented by a DocumentReference object. Each of them + * Each reference is represented by a DocumentKeyReference object. Each of them * contains enough information to uniquely identify the reference. They are all * stored primarily in a set sorted by key. A document is considered garbage if * there's no references in that set (this can be efficiently checked thanks to @@ -81,10 +81,11 @@ class ReferenceSet { bool ContainsKey(const model::DocumentKey& key); private: - void RemoveReference(const DocumentReference& reference); + void RemoveReference(const DocumentKeyReference& reference); - immutable::SortedSet by_key_; - immutable::SortedSet by_id_; + immutable::SortedSet + by_key_; + immutable::SortedSet by_id_; }; } // namespace local diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt index 170bf8e83a3..c83dd12b726 100644 --- a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -50,6 +50,7 @@ cc_library( DEPENDS absl_optional absl_strings + firebase_firestore_api_input_validation firebase_firestore_immutable firebase_firestore_util firebase_firestore_types diff --git a/Firestore/core/src/firebase/firestore/model/document.cc b/Firestore/core/src/firebase/firestore/model/document.cc index 06deb50576b..da540c88324 100644 --- a/Firestore/core/src/firebase/firestore/model/document.cc +++ b/Firestore/core/src/firebase/firestore/model/document.cc @@ -24,7 +24,7 @@ namespace firebase { namespace firestore { namespace model { -Document::Document(FieldValue&& data, +Document::Document(ObjectValue&& data, DocumentKey key, SnapshotVersion version, DocumentState document_state) @@ -32,7 +32,6 @@ Document::Document(FieldValue&& data, data_(std::move(data)), document_state_(document_state) { set_type(Type::Document); - HARD_ASSERT(FieldValue::Type::Object == data.type()); } bool Document::Equals(const MaybeDocument& other) const { diff --git a/Firestore/core/src/firebase/firestore/model/document.h b/Firestore/core/src/firebase/firestore/model/document.h index 9afa327f64b..acd04212572 100644 --- a/Firestore/core/src/firebase/firestore/model/document.h +++ b/Firestore/core/src/firebase/firestore/model/document.h @@ -50,14 +50,14 @@ enum class DocumentState { class Document : public MaybeDocument { public: /** - * Construct a document. FieldValue must be passed by rvalue. + * Construct a document. ObjectValue must be passed by rvalue. */ - Document(FieldValue&& data, + Document(ObjectValue&& data, DocumentKey key, SnapshotVersion version, DocumentState document_state); - const FieldValue& data() const { + const ObjectValue& data() const { return data_; } @@ -81,7 +81,7 @@ class Document : public MaybeDocument { bool Equals(const MaybeDocument& other) const override; private: - FieldValue data_; // This is of type Object. + ObjectValue data_; DocumentState document_state_; }; diff --git a/Firestore/core/src/firebase/firestore/model/document_key.h b/Firestore/core/src/firebase/firestore/model/document_key.h index ab4e3373648..2443e26a9fb 100644 --- a/Firestore/core/src/firebase/firestore/model/document_key.h +++ b/Firestore/core/src/firebase/firestore/model/document_key.h @@ -64,7 +64,7 @@ class DocumentKey { * Creates and returns a new document key using '/' to split the string into * segments. */ - static DocumentKey FromPathString(const absl::string_view path) { + static DocumentKey FromPathString(absl::string_view path) { return DocumentKey{ResourcePath::FromString(path)}; } @@ -86,6 +86,12 @@ class DocumentKey { return path_ ? *path_ : Empty().path(); } + /** Returns true if the document is in the specified collectionId. */ + bool HasCollectionId(absl::string_view collection_id) const { + size_t size = path().size(); + return size >= 2 && path()[size - 2] == collection_id; + } + private: // This is an optimization to make passing DocumentKey around cheaper (it's // copied often). diff --git a/Firestore/core/src/firebase/firestore/model/document_set.h b/Firestore/core/src/firebase/firestore/model/document_set.h new file mode 100644 index 00000000000..003f1050a3c --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/document_set.h @@ -0,0 +1,174 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_SET_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_SET_H_ + +#if !defined(__OBJC__) +#error "This header only supports Objective-C++" +#endif // !defined(__OBJC__) + +#import + +#include +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/immutable/sorted_container.h" +#include "Firestore/core/src/firebase/firestore/immutable/sorted_set.h" +#include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_map.h" +#include "Firestore/core/src/firebase/firestore/util/comparison.h" + +@class FSTDocument; + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace model { + +/** + * A C++ comparator that returns less-than, implemented by delegating to + * an NSComparator. + */ +class DocumentSetComparator { + public: + explicit DocumentSetComparator(NSComparator delegate = nil) + : delegate_(delegate) { + } + + bool operator()(FSTDocument* lhs, FSTDocument* rhs) const { + return delegate_(lhs, rhs) == NSOrderedAscending; + } + + private: + NSComparator delegate_; +}; + +/** + * DocumentSet is an immutable (copy-on-write) collection that holds documents + * in order specified by the provided comparator. We always add a document key + * comparator on top of what is provided to guarantee document equality based on + * the key. + */ +class DocumentSet : public immutable::SortedContainer, + public util::Equatable { + public: + /** + * The type of the main collection of documents in an DocumentSet. + * @see sorted_set_. + */ + using SetType = immutable::SortedSet; + + // STL container types + using value_type = FSTDocument*; + using const_iterator = SetType::const_iterator; + + /** + * Creates a new, empty DocumentSet sorted by the given comparator, then by + * keys. + */ + explicit DocumentSet(NSComparator comparator); + + size_t size() const { + return index_.size(); + } + + /** Returns true if the dictionary contains no elements. */ + bool empty() const { + return index_.empty(); + } + + /** Returns true if this set contains a document with the given key. */ + bool ContainsKey(const DocumentKey& key) const; + + SetType::const_iterator begin() const { + return sorted_set_.begin(); + } + SetType::const_iterator end() const { + return sorted_set_.end(); + } + + /** + * Returns the document from this set with the given key if it exists or nil + * if it doesn't. + */ + FSTDocument* _Nullable GetDocument(const DocumentKey& key) const; + + /** + * Returns the first document in the set according to its built in ordering, + * or nil if the set is empty. + */ + FSTDocument* _Nullable GetFirstDocument() const; + + /** + * Returns the last document in the set according to its built in ordering, or + * nil if the set is empty. + */ + FSTDocument* _Nullable GetLastDocument() const; + + /** + * Returns the index of the document with the provided key in the document + * set. Returns `npos` if the key is not present. + */ + size_t IndexOf(const DocumentKey& key) const; + + /** Returns a new DocumentSet that contains the given document. */ + DocumentSet insert(FSTDocument* _Nullable document) const; + + /** + * Returns a new DocumentSet that excludes any document associated with + * the given key. + */ + DocumentSet erase(const DocumentKey& key) const; + + friend bool operator==(const DocumentSet& lhs, const DocumentSet& rhs); + + std::string ToString() const; + friend std::ostream& operator<<(std::ostream& os, const DocumentSet& set); + + size_t Hash() const; + + private: + DocumentSet(DocumentMap&& index, SetType&& sorted_set) + : index_(std::move(index)), sorted_set_(std::move(sorted_set)) { + } + + /** + * An index of the documents in the DocumentSet, indexed by document key. + * The index exists to guarantee the uniqueness of document keys in the set + * and to allow lookup and removal of documents by key. + */ + DocumentMap index_; + + /** + * The main collection of documents in the DocumentSet. The documents are + * ordered by a comparator supplied from a query. The SetType collection + * exists in addition to the index to allow ordered traversal of the + * DocumentSet. + */ + SetType sorted_set_; +}; + +} // namespace model +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_SET_H_ diff --git a/Firestore/core/src/firebase/firestore/model/document_set.mm b/Firestore/core/src/firebase/firestore/model/document_set.mm new file mode 100644 index 00000000000..47cb3d263dd --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/document_set.mm @@ -0,0 +1,121 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/document_set.h" + +#include +#include + +#import "Firestore/Source/Model/FSTDocument.h" + +#include "Firestore/core/src/firebase/firestore/immutable/sorted_set.h" +#include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/util/objc_compatibility.h" +#include "absl/algorithm/container.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace model { + +namespace objc = util::objc; +using immutable::SortedSet; + +DocumentSet::DocumentSet(NSComparator comparator) + : index_{}, sorted_set_{DocumentSetComparator{comparator}} { +} + +bool operator==(const DocumentSet& lhs, const DocumentSet& rhs) { + return absl::c_equal(lhs.sorted_set_, rhs.sorted_set_, + [](FSTDocument* left_doc, FSTDocument* right_doc) { + return [left_doc isEqual:right_doc]; + }); +} + +std::string DocumentSet::ToString() const { + return util::ToString(sorted_set_); +} + +std::ostream& operator<<(std::ostream& os, const DocumentSet& set) { + return os << set.ToString(); +} + +size_t DocumentSet::Hash() const { + size_t hash = 0; + for (FSTDocument* doc : sorted_set_) { + hash = 31 * hash + [doc hash]; + } + return hash; +} + +bool DocumentSet::ContainsKey(const DocumentKey& key) const { + return index_.underlying_map().find(key) != index_.underlying_map().end(); +} + +FSTDocument* _Nullable DocumentSet::GetDocument(const DocumentKey& key) const { + auto found = index_.underlying_map().find(key); + return found != index_.underlying_map().end() + ? static_cast(found->second) + : nil; +} + +FSTDocument* _Nullable DocumentSet::GetFirstDocument() const { + auto result = sorted_set_.min(); + return result != sorted_set_.end() ? *result : nil; +} + +FSTDocument* _Nullable DocumentSet::GetLastDocument() const { + auto result = sorted_set_.max(); + return result != sorted_set_.end() ? *result : nil; +} + +size_t DocumentSet::IndexOf(const DocumentKey& key) const { + FSTDocument* doc = GetDocument(key); + return doc ? sorted_set_.find_index(doc) : npos; +} + +DocumentSet DocumentSet::insert(FSTDocument* _Nullable document) const { + // TODO(mcg): look into making document nonnull. + if (!document) { + return *this; + } + + // Remove any prior mapping of the document's key before adding, preventing + // sortedSet from accumulating values that aren't in the index. + DocumentSet removed = erase(document.key); + + DocumentMap index = removed.index_.insert(document.key, document); + SetType set = removed.sorted_set_.insert(document); + return {std::move(index), std::move(set)}; +} + +DocumentSet DocumentSet::erase(const DocumentKey& key) const { + FSTDocument* doc = GetDocument(key); + if (!doc) { + return *this; + } + + DocumentMap index = index_.erase(key); + SetType set = sorted_set_.erase(doc); + return {std::move(index), std::move(set)}; +} + +} // namespace model +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index b1dcad75a65..fd06e98eb79 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -19,6 +19,7 @@ #include #include +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" @@ -28,6 +29,8 @@ namespace firebase { namespace firestore { namespace model { +using api::ThrowInvalidArgument; + namespace { /** @@ -78,6 +81,28 @@ struct JoinEscaped { }; } // namespace +FieldPath FieldPath::FromDotSeparatedString(absl::string_view path) { + if (path.find_first_of("~*/[]") != absl::string_view::npos) { + ThrowInvalidArgument( + "Invalid field path (%s). Paths must not contain '~', '*', '/', '[', " + "or ']'", + path); + } + + SegmentsT segments = + absl::StrSplit(path, '.', [path](absl::string_view segment) { + if (segment.empty()) { + ThrowInvalidArgument( + "Invalid field path (%s). Paths must not be empty, begin with " + "'.', end with '.', or contain '..'", + path); + } + return true; + }); + + return FieldPath(std::move(segments)); +} + FieldPath FieldPath::FromServerFormat(const absl::string_view path) { SegmentsT segments; std::string segment; diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index ce3527d920c..609c9f54307 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -21,6 +21,7 @@ #include #include +#include "Firestore/core/src/firebase/firestore/api/input_validation.h" #include "Firestore/core/src/firebase/firestore/model/base_path.h" #include "absl/strings/string_view.h" @@ -51,6 +52,25 @@ class FieldPath : public impl::BasePath { explicit FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} { } + /** + * Creates and returns a new path from a dot-separated field-path string, + * where path segments are separated by a dot ".". + * + * PORTING NOTE: We define this on the model class to avoid having a tiny + * api::FieldPath wrapper class. + */ + static FieldPath FromDotSeparatedString(absl::string_view path); + + /** + * Creates and returns a new path from a set of segments received from the + * public API. + */ + static FieldPath FromSegments(SegmentsT&& segments) { + ValidateSegments(segments); + FieldPath path(std::move(segments)); + return path; + } + /** * Creates and returns a new path from the server formatted field-path string, * where path segments are separated by a dot "." and optionally encoded using @@ -85,6 +105,22 @@ class FieldPath : public impl::BasePath { bool operator>=(const FieldPath& rhs) const { return BasePath::operator>=(rhs); } + + private: + static void ValidateSegments(const SegmentsT& segments) { + if (segments.empty()) { + api::ThrowInvalidArgument( + "Invalid field path. Provided names must not be empty."); + } + + for (size_t i = 0; i < segments.size(); i++) { + if (segments[i].empty()) { + api::ThrowInvalidArgument( + "Invalid field name at index %s. Field names must not be empty.", + i); + } + } + } }; } // namespace model diff --git a/Firestore/core/src/firebase/firestore/model/field_value.cc b/Firestore/core/src/firebase/firestore/model/field_value.cc index 53e197a9113..e5064479c0e 100644 --- a/Firestore/core/src/firebase/firestore/model/field_value.cc +++ b/Firestore/core/src/firebase/firestore/model/field_value.cc @@ -23,8 +23,10 @@ #include #include +#include "Firestore/core/src/firebase/firestore/immutable/sorted_map.h" #include "Firestore/core/src/firebase/firestore/util/comparison.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "Firestore/core/src/firebase/firestore/util/hashing.h" #include "absl/memory/memory.h" using firebase::firestore::util::Comparator; @@ -91,8 +93,8 @@ FieldValue& FieldValue::operator=(const FieldValue& value) { } case Type::Object: { // copy-and-swap - ObjectValue::Map tmp = value.object_value_->internal_value; - std::swap(object_value_->internal_value, tmp); + Map tmp = *value.object_value_; + std::swap(*object_value_, tmp); break; } default: @@ -143,45 +145,41 @@ bool FieldValue::Comparable(Type lhs, Type rhs) { } } -FieldValue FieldValue::Set(const FieldPath& field_path, - const FieldValue& value) const { - HARD_ASSERT(type() == Type::Object, - "Cannot set field for non-object FieldValue"); +// TODO(rsgowman): Reorder this file to match its header. +ObjectValue ObjectValue::Set(const FieldPath& field_path, + const FieldValue& value) const { HARD_ASSERT(!field_path.empty(), "Cannot set field for empty path on FieldValue"); // Set the value by recursively calling on child object. const std::string& child_name = field_path.first_segment(); - const ObjectValue::Map& object_map = object_value_->internal_value; if (field_path.size() == 1) { return SetChild(child_name, value); } else { - FieldValue child; - const auto iter = object_map.find(child_name); - if (iter != object_map.end() && iter->second.type() == Type::Object) { - child = iter->second; - } else { - child = EmptyObject(); + ObjectValue child = ObjectValue::Empty(); + const auto iter = fv_.object_value_->find(child_name); + if (iter != fv_.object_value_->end() && + iter->second.type() == Type::Object) { + child = ObjectValue(iter->second); } - FieldValue new_child = child.Set(field_path.PopFirst(), value); - return SetChild(child_name, new_child); + ObjectValue new_child = child.Set(field_path.PopFirst(), value); + return SetChild(child_name, new_child.fv_); } } -FieldValue FieldValue::Delete(const FieldPath& field_path) const { - HARD_ASSERT(type() == Type::Object, - "Cannot delete field for non-object FieldValue"); +ObjectValue ObjectValue::Delete(const FieldPath& field_path) const { HARD_ASSERT(!field_path.empty(), "Cannot delete field for empty path on FieldValue"); // Delete the value by recursively calling on child object. const std::string& child_name = field_path.first_segment(); - const ObjectValue::Map& object_map = object_value_->internal_value; if (field_path.size() == 1) { - return FieldValue::FromMap(object_map.erase(child_name)); + return ObjectValue::FromMap(fv_.object_value_->erase(child_name)); } else { - const auto iter = object_map.find(child_name); - if (iter != object_map.end() && iter->second.type() == Type::Object) { - FieldValue new_child = iter->second.Delete(field_path.PopFirst()); - return SetChild(child_name, new_child); + const auto iter = fv_.object_value_->find(child_name); + if (iter != fv_.object_value_->end() && + iter->second.type() == Type::Object) { + ObjectValue new_child = + ObjectValue(iter->second).Delete(field_path.PopFirst()); + return SetChild(child_name, new_child.fv_); } else { // If the found value isn't an object, it cannot contain the remaining // segments of the path. We don't actually change a primitive value to @@ -191,17 +189,14 @@ FieldValue FieldValue::Delete(const FieldPath& field_path) const { } } -absl::optional FieldValue::Get(const FieldPath& field_path) const { - HARD_ASSERT(type() == Type::Object, - "Cannot get field for non-object FieldValue"); - const FieldValue* current = this; +absl::optional ObjectValue::Get(const FieldPath& field_path) const { + const FieldValue* current = &this->fv_; for (const auto& path : field_path) { if (current->type() != Type::Object) { return absl::nullopt; } - const ObjectValue::Map& object_map = current->object_value_->internal_value; - const auto iter = object_map.find(path); - if (iter == object_map.end()) { + const auto iter = current->object_value_->find(path); + if (iter == current->object_value_->end()) { return absl::nullopt; } else { current = &iter->second; @@ -210,12 +205,9 @@ absl::optional FieldValue::Get(const FieldPath& field_path) const { return *current; } -FieldValue FieldValue::SetChild(const std::string& child_name, - const FieldValue& value) const { - HARD_ASSERT(type() == Type::Object, - "Cannot set child for non-object FieldValue"); - return FieldValue::FromMap( - object_value_->internal_value.insert(child_name, value)); +ObjectValue ObjectValue::SetChild(const std::string& child_name, + const FieldValue& value) const { + return ObjectValue::FromMap(fv_.object_value_->insert(child_name, value)); } FieldValue FieldValue::Null() { @@ -239,7 +231,7 @@ FieldValue FieldValue::Nan() { } FieldValue FieldValue::EmptyObject() { - return FieldValue::FromMap(ObjectValue::Empty()); + return FieldValue::FromMap(FieldValue::Map()); } FieldValue FieldValue::FromInteger(int64_t value) { @@ -344,19 +336,47 @@ FieldValue FieldValue::FromArray(std::vector&& value) { return result; } -FieldValue FieldValue::FromMap(const ObjectValue::Map& value) { - ObjectValue::Map copy(value); +FieldValue FieldValue::FromMap(const FieldValue::Map& value) { + FieldValue::Map copy(value); return FromMap(std::move(copy)); } -FieldValue FieldValue::FromMap(ObjectValue::Map&& value) { +FieldValue FieldValue::FromMap(FieldValue::Map&& value) { FieldValue result; result.SwitchTo(Type::Object); - std::swap(result.object_value_->internal_value, value); + std::swap(*result.object_value_, value); return result; } -bool operator<(const ObjectValue::Map& lhs, const ObjectValue::Map& rhs) { +size_t FieldValue::Hash() const { + switch (type()) { + case FieldValue::Type::Null: + HARD_FAIL("TODO(rsgowman): Implement"); + + case FieldValue::Type::Boolean: + return util::Hash(boolean_value()); + + case FieldValue::Type::Integer: + case FieldValue::Type::Double: + case FieldValue::Type::Timestamp: + case FieldValue::Type::ServerTimestamp: + HARD_FAIL("TODO(rsgowman): Implement"); + + case FieldValue::Type::String: + return util::Hash(string_value()); + + case FieldValue::Type::Blob: + case FieldValue::Type::Reference: + case FieldValue::Type::GeoPoint: + case FieldValue::Type::Array: + case FieldValue::Type::Object: + HARD_FAIL("TODO(rsgowman): Implement"); + } + + UNREACHABLE(); +} + +bool operator<(const FieldValue::Map& lhs, const FieldValue::Map& rhs) { return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); } @@ -454,7 +474,7 @@ void FieldValue::SwitchTo(const Type type) { array_value_.~unique_ptr>(); break; case Type::Object: - object_value_.~unique_ptr(); + object_value_.~unique_ptr(); break; default: {} // The other types where there is nothing to worry about. } @@ -491,13 +511,20 @@ void FieldValue::SwitchTo(const Type type) { absl::make_unique>()); break; case Type::Object: - new (&object_value_) - std::unique_ptr(absl::make_unique()); + new (&object_value_) std::unique_ptr(absl::make_unique()); break; default: {} // The other types where there is nothing to worry about. } } +ObjectValue ObjectValue::FromMap(const FieldValue::Map& value) { + return ObjectValue(FieldValue::FromMap(value)); +} + +ObjectValue ObjectValue::FromMap(FieldValue::Map&& value) { + return ObjectValue(FieldValue::FromMap(std::move(value))); +} + } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/field_value.h b/Firestore/core/src/firebase/firestore/model/field_value.h index c6902d3dfd7..0ed39d5f214 100644 --- a/Firestore/core/src/firebase/firestore/model/field_value.h +++ b/Firestore/core/src/firebase/firestore/model/field_value.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "Firestore/core/include/firebase/firestore/geo_point.h" @@ -31,6 +32,10 @@ #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "absl/types/optional.h" +#if __OBJC__ +@class FSTFieldValue; +#endif // __OBJC__ + namespace firebase { namespace firestore { namespace model { @@ -46,8 +51,6 @@ struct ReferenceValue { const DatabaseId* database_id; }; -struct ObjectValue; - /** * tagged-union class representing an immutable data value as stored in * Firestore. FieldValue represents all the different kinds of values @@ -55,6 +58,8 @@ struct ObjectValue; */ class FieldValue { public: + using Map = immutable::SortedMap; + /** * All the different kinds of values that can be stored in fields in * a document. The types of the same comparison order should be defined @@ -79,6 +84,14 @@ class FieldValue { // position instead, see the doc comment above. }; + /** + * Checks if the given type is a numeric, such as Type::Integer or + * Type::Double. + */ + static bool IsNumber(Type type) { + return type == Type::Integer || type == Type::Double; + } + FieldValue() { } @@ -92,6 +105,10 @@ class FieldValue { FieldValue& operator=(const FieldValue& value); FieldValue& operator=(FieldValue&& value); +#if __OBJC__ + FSTFieldValue* Wrap() &&; +#endif // __OBJC__ + /** Returns the true type for this value. */ Type type() const { return tag_; @@ -116,6 +133,11 @@ class FieldValue { return integer_value_; } + double double_value() const { + HARD_ASSERT(tag_ == Type::Double); + return double_value_; + } + Timestamp timestamp_value() const { HARD_ASSERT(tag_ == Type::Timestamp); return *timestamp_value_; @@ -126,38 +148,20 @@ class FieldValue { return *string_value_; } - const ObjectValue& object_value() const { - HARD_ASSERT(tag_ == Type::Object); - return *object_value_; + const std::vector& blob_value() const { + HARD_ASSERT(tag_ == Type::Blob); + return *blob_value_; } - /** - * Returns a FieldValue with the field at the named path set to value. - * Any absent parent of the field will also be created accordingly. - * - * @param field_path The field path to set. Cannot be empty. - * @param value The value to set. - * @return A new FieldValue with the field set. - */ - FieldValue Set(const FieldPath& field_path, const FieldValue& value) const; - - /** - * Returns a FieldValue with the field path deleted. If there is no field at - * the specified path, the returned value is an identical copy. - * - * @param field_path The field path to remove. Cannot be empty. - * @return A new FieldValue with the field path removed. - */ - FieldValue Delete(const FieldPath& field_path) const; + const GeoPoint& geo_point_value() const { + HARD_ASSERT(tag_ == Type::GeoPoint); + return *geo_point_value_; + } - /** - * Returns the value at the given path or absl::nullopt. If the path is empty, - * an identical copy of the FieldValue is returned. - * - * @param field_path the path to search. - * @return The value at the path or absl::nullopt if it doesn't exist. - */ - absl::optional Get(const FieldPath& field_path) const; + const std::vector& array_value() const { + HARD_ASSERT(tag_ == Type::Array); + return *array_value_; + } /** factory methods. */ static FieldValue Null(); @@ -183,14 +187,16 @@ class FieldValue { static FieldValue FromGeoPoint(const GeoPoint& value); static FieldValue FromArray(const std::vector& value); static FieldValue FromArray(std::vector&& value); - static FieldValue FromMap( - const immutable::SortedMap& value); - static FieldValue FromMap( - immutable::SortedMap&& value); + static FieldValue FromMap(const Map& value); + static FieldValue FromMap(Map&& value); + + size_t Hash() const; friend bool operator<(const FieldValue& lhs, const FieldValue& rhs); private: + friend class ObjectValue; + explicit FieldValue(bool value) : tag_(Type::Boolean), boolean_value_(value) { } @@ -199,9 +205,6 @@ class FieldValue { */ void SwitchTo(Type type); - FieldValue SetChild(const std::string& child_name, - const FieldValue& value) const; - Type tag_ = Type::Null; union { // There is no null type as tag_ alone is enough for Null FieldValue. @@ -216,27 +219,72 @@ class FieldValue { std::unique_ptr reference_value_; std::unique_ptr geo_point_value_; std::unique_ptr> array_value_; - std::unique_ptr object_value_; + std::unique_ptr object_value_; }; }; -// TODO(rsgowman): Expand this to roughly match the java class -// c.g.f.f.model.value.ObjectValue. Probably move it to a similar namespace as -// well. (FieldValue itself is also in the value package in java.) Also do the -// same with the other FooValue values that FieldValue can return. -struct ObjectValue { - // TODO(rsgowman): These will eventually be private. We do want the serializer - // to be able to directly access these (possibly implying 'friend' usage, or a - // getInternalValue() like java has.) - using Map = immutable::SortedMap; - Map internal_value; +/** A structured object value stored in Firestore. */ +class ObjectValue { + public: + explicit ObjectValue(FieldValue fv) : fv_(std::move(fv)) { + HARD_ASSERT(fv_.type() == FieldValue::Type::Object); + } + + static ObjectValue Empty() { + return ObjectValue(FieldValue::EmptyObject()); + } + + static ObjectValue FromMap(const FieldValue::Map& value); + static ObjectValue FromMap(FieldValue::Map&& value); + + /** + * Returns the value at the given path or absl::nullopt. If the path is empty, + * an identical copy of the FieldValue is returned. + * + * @param field_path the path to search. + * @return The value at the path or absl::nullopt if it doesn't exist. + */ + absl::optional Get(const FieldPath& field_path) const; + + /** + * Returns a FieldValue with the field at the named path set to value. + * Any absent parent of the field will also be created accordingly. + * + * @param field_path The field path to set. Cannot be empty. + * @param value The value to set. + * @return A new FieldValue with the field set. + */ + ObjectValue Set(const FieldPath& field_path, const FieldValue& value) const; - static ObjectValue::Map Empty() { - return Map(); + /** + * Returns a FieldValue with the field path deleted. If there is no field at + * the specified path, the returned value is an identical copy. + * + * @param field_path The field path to remove. Cannot be empty. + * @return A new FieldValue with the field path removed. + */ + ObjectValue Delete(const FieldPath& field_path) const; + + // TODO(rsgowman): Add Value() method? + // + // Java has a value() method which returns a (non-immutable) java.util.Map, + // which is a copy of the immutable map, but with some fields (such as server + // timestamps) optionally resolved. Do we need the same here? + + const FieldValue::Map& GetInternalValue() const { + return *fv_.object_value_; } + + private: + friend bool operator<(const ObjectValue& lhs, const ObjectValue& rhs); + + ObjectValue SetChild(const std::string& child_name, + const FieldValue& value) const; + + FieldValue fv_; }; -bool operator<(const ObjectValue::Map& lhs, const ObjectValue::Map& rhs); +bool operator<(const FieldValue::Map& lhs, const FieldValue::Map& rhs); /** Compares against another FieldValue. */ bool operator<(const FieldValue& lhs, const FieldValue& rhs); @@ -263,7 +311,7 @@ inline bool operator==(const FieldValue& lhs, const FieldValue& rhs) { /** Compares against another ObjectValue. */ inline bool operator<(const ObjectValue& lhs, const ObjectValue& rhs) { - return lhs.internal_value < rhs.internal_value; + return lhs.fv_ < rhs.fv_; } inline bool operator>(const ObjectValue& lhs, const ObjectValue& rhs) { diff --git a/Firestore/core/src/firebase/firestore/model/field_value.mm b/Firestore/core/src/firebase/firestore/model/field_value.mm new file mode 100644 index 00000000000..d914e467078 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/field_value.mm @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/field_value.h" + +#import "Firestore/Source/Model/FSTFieldValue.h" + +FSTFieldValue* FieldValue::Wrap() && { + return [FSTDelegateValue delegateWithValue:std::move(*this)]; +} diff --git a/Firestore/core/src/firebase/firestore/model/mutation.cc b/Firestore/core/src/firebase/firestore/model/mutation.cc index d2ef4ebc3d3..e863b34520a 100644 --- a/Firestore/core/src/firebase/firestore/model/mutation.cc +++ b/Firestore/core/src/firebase/firestore/model/mutation.cc @@ -21,6 +21,7 @@ #include "Firestore/core/src/firebase/firestore/model/document.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" #include "Firestore/core/src/firebase/firestore/model/no_document.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" @@ -54,12 +55,10 @@ bool Mutation::equal_to(const Mutation& other) const { } SetMutation::SetMutation(DocumentKey&& key, - FieldValue&& value, + ObjectValue&& value, Precondition&& precondition) : Mutation(std::move(key), std::move(precondition)), value_(std::move(value)) { - // TODO(rsgowman): convert param to ObjectValue instead of FieldValue? - HARD_ASSERT(value_.type() == FieldValue::Type::Object); } MaybeDocumentPtr SetMutation::ApplyToRemoteDocument( @@ -74,7 +73,7 @@ MaybeDocumentPtr SetMutation::ApplyToRemoteDocument( // the server has accepted the mutation so the precondition must have held. const SnapshotVersion& version = mutation_result.version(); - return absl::make_unique(FieldValue(value_), key(), version, + return absl::make_unique(ObjectValue(value_), key(), version, DocumentState::kCommittedMutations); } @@ -89,7 +88,7 @@ MaybeDocumentPtr SetMutation::ApplyToLocalView( } SnapshotVersion version = GetPostMutationVersion(maybe_doc.get()); - return absl::make_unique(FieldValue(value_), key(), version, + return absl::make_unique(ObjectValue(value_), key(), version, DocumentState::kLocalMutations); } @@ -99,14 +98,12 @@ bool SetMutation::equal_to(const Mutation& other) const { } PatchMutation::PatchMutation(DocumentKey&& key, - FieldValue&& value, + ObjectValue&& value, FieldMask&& mask, Precondition&& precondition) : Mutation(std::move(key), std::move(precondition)), value_(std::move(value)), mask_(std::move(mask)) { - // TODO(rsgowman): convert param to ObjectValue instead of FieldValue? - HARD_ASSERT(value_.type() == FieldValue::Type::Object); } MaybeDocumentPtr PatchMutation::ApplyToRemoteDocument( @@ -131,7 +128,7 @@ MaybeDocumentPtr PatchMutation::ApplyToRemoteDocument( } const SnapshotVersion& version = mutation_result.version(); - FieldValue new_data = PatchDocument(maybe_doc.get()); + ObjectValue new_data = PatchDocument(maybe_doc.get()); return absl::make_unique(std::move(new_data), key(), version, DocumentState::kCommittedMutations); } @@ -147,21 +144,20 @@ MaybeDocumentPtr PatchMutation::ApplyToLocalView( } SnapshotVersion version = GetPostMutationVersion(maybe_doc.get()); - FieldValue new_data = PatchDocument(maybe_doc.get()); + ObjectValue new_data = PatchDocument(maybe_doc.get()); return absl::make_unique(std::move(new_data), key(), version, DocumentState::kLocalMutations); } -FieldValue PatchMutation::PatchDocument(const MaybeDocument* maybe_doc) const { +ObjectValue PatchMutation::PatchDocument(const MaybeDocument* maybe_doc) const { if (maybe_doc && maybe_doc->type() == MaybeDocument::Type::Document) { return PatchObject(static_cast(maybe_doc)->data()); } else { - return PatchObject(FieldValue::EmptyObject()); + return PatchObject(ObjectValue::Empty()); } } -FieldValue PatchMutation::PatchObject(FieldValue obj) const { - HARD_ASSERT(obj.type() == FieldValue::Type::Object); +ObjectValue PatchMutation::PatchObject(ObjectValue obj) const { for (const FieldPath& path : mask_) { if (!path.empty()) { absl::optional new_value = value_.Get(path); diff --git a/Firestore/core/src/firebase/firestore/model/mutation.h b/Firestore/core/src/firebase/firestore/model/mutation.h index 08b4c35aeef..8e4ad562405 100644 --- a/Firestore/core/src/firebase/firestore/model/mutation.h +++ b/Firestore/core/src/firebase/firestore/model/mutation.h @@ -46,7 +46,7 @@ class MutationResult { public: MutationResult( SnapshotVersion&& version, - const std::shared_ptr>& transform_results) + const std::shared_ptr>& transform_results) : version_(std::move(version)), transform_results_(std::move(transform_results)) { } @@ -67,19 +67,19 @@ class MutationResult { /** * The resulting fields returned from the backend after a TransformMutation - * has been committed. Contains one FieldValue for each FieldTransform + * has been committed. Contains one ObjectValue for each FieldTransform * that was in the mutation. * * Will be null if the mutation was not a TransformMutation. */ - const std::shared_ptr>& transform_results() + const std::shared_ptr>& transform_results() const { return transform_results_; } private: const SnapshotVersion version_; - const std::shared_ptr> transform_results_; + const std::shared_ptr> transform_results_; }; /** @@ -217,7 +217,7 @@ inline bool operator!=(const Mutation& lhs, const Mutation& rhs) { class SetMutation : public Mutation { public: SetMutation(DocumentKey&& key, - FieldValue&& value, + ObjectValue&& value, Precondition&& precondition); Type type() const override { @@ -234,7 +234,7 @@ class SetMutation : public Mutation { const Timestamp& local_write_time) const override; /** Returns the object value to use when setting the document. */ - const FieldValue& value() const { + const ObjectValue& value() const { return value_; } @@ -242,7 +242,7 @@ class SetMutation : public Mutation { bool equal_to(const Mutation& other) const override; private: - const FieldValue value_; + const ObjectValue value_; }; /** @@ -261,7 +261,7 @@ class SetMutation : public Mutation { class PatchMutation : public Mutation { public: PatchMutation(DocumentKey&& key, - FieldValue&& value, + ObjectValue&& value, FieldMask&& mask, Precondition&& precondition); @@ -281,7 +281,7 @@ class PatchMutation : public Mutation { /** * Returns the fields and associated values to use when patching the document. */ - const FieldValue& value() const { + const ObjectValue& value() const { return value_; } @@ -297,10 +297,10 @@ class PatchMutation : public Mutation { bool equal_to(const Mutation& other) const override; private: - FieldValue PatchDocument(const MaybeDocument* maybe_doc) const; - FieldValue PatchObject(FieldValue obj) const; + ObjectValue PatchDocument(const MaybeDocument* maybe_doc) const; + ObjectValue PatchObject(ObjectValue obj) const; - const FieldValue value_; + const ObjectValue value_; const FieldMask mask_; }; diff --git a/Firestore/core/src/firebase/firestore/model/transform_operations.h b/Firestore/core/src/firebase/firestore/model/transform_operations.h index c4ac9849a48..1930bc72954 100644 --- a/Firestore/core/src/firebase/firestore/model/transform_operations.h +++ b/Firestore/core/src/firebase/firestore/model/transform_operations.h @@ -27,6 +27,7 @@ #import "Firestore/Source/Model/FSTFieldValue.h" #include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" namespace firebase { namespace firestore { @@ -259,18 +260,18 @@ class NumericIncrementTransform : public TransformOperation { FIRTimestamp* /* localWriteTime */) const override { // Return an integer value only if the previous value and the operand is an // integer. - if ([previousValue isKindOfClass:[FSTIntegerValue class]] && - [operand_ isKindOfClass:[FSTIntegerValue class]]) { + if (previousValue.type == FieldValue::Type::Integer && + operand_.type == FieldValue::Type::Integer) { int64_t sum = SafeIncrement( (static_cast(previousValue)).internalValue, (static_cast(operand_)).internalValue); return [FSTIntegerValue integerValue:sum]; - } else if ([previousValue isKindOfClass:[FSTIntegerValue class]]) { + } else if (previousValue.type == FieldValue::Type::Integer) { double sum = (static_cast(previousValue)).internalValue + OperandAsDouble(); return [FSTDoubleValue doubleValue:sum]; - } else if ([previousValue isKindOfClass:[FSTDoubleValue class]]) { + } else if (previousValue.type == FieldValue::Type::Double) { double sum = (static_cast(previousValue)).internalValue + OperandAsDouble(); return [FSTDoubleValue doubleValue:sum]; @@ -282,8 +283,7 @@ class NumericIncrementTransform : public TransformOperation { } FSTFieldValue* ApplyToRemoteDocument( - FSTFieldValue* previousValue, - FSTFieldValue* transformResult) const override { + FSTFieldValue*, FSTFieldValue* transformResult) const override { return transformResult; } @@ -330,9 +330,9 @@ class NumericIncrementTransform : public TransformOperation { } double OperandAsDouble() const { - if ([operand_ isKindOfClass:[FSTDoubleValue class]]) { + if (operand_.type == FieldValue::Type::Double) { return (static_cast(operand_)).internalValue; - } else if ([operand_ isKindOfClass:[FSTIntegerValue class]]) { + } else if (operand_.type == FieldValue::Type::Integer) { return (static_cast(operand_)).internalValue; } else { HARD_FAIL("Expected 'operand' to be of FSTNumerValue type, but was %s", diff --git a/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.cc b/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.cc index 89a9be66db4..90850021e76 100644 --- a/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.cc +++ b/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.cc @@ -19,12 +19,18 @@ #include #include +#include "Firestore/core/src/firebase/firestore/nanopb/nanopb_util.h" + namespace firebase { namespace firestore { namespace nanopb { +String::~String() { + std::free(bytes_); +} + /* static */ pb_bytes_array_t* String::MakeBytesArray(absl::string_view value) { - auto size = static_cast(value.size()); + pb_size_t size = CheckedSize(value.size()); // Allocate one extra byte for the null terminator that's not necessarily // there in a string_view. As long as we're making a copy, might as well diff --git a/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.h b/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.h index 444d53d9541..53a96682306 100644 --- a/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.h +++ b/Firestore/core/src/firebase/firestore/nanopb/nanopb_string.h @@ -72,9 +72,7 @@ class String : public util::Comparable { swap(*this, other); } - ~String() { - delete bytes_; - } + ~String(); String& operator=(String other) { swap(*this, other); diff --git a/Firestore/core/src/firebase/firestore/nanopb/nanopb_util.h b/Firestore/core/src/firebase/firestore/nanopb/nanopb_util.h new file mode 100644 index 00000000000..667c28a6b6d --- /dev/null +++ b/Firestore/core/src/firebase/firestore/nanopb/nanopb_util.h @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_NANOPB_UTIL_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_NANOPB_UTIL_H_ + +#include + +#include "Firestore/core/src/firebase/firestore/util/hard_assert.h" + +namespace firebase { +namespace firestore { +namespace nanopb { + +/** + * Static casts the given size_t value down to a nanopb compatible size, after + * asserting that the value isn't out of range. + */ +inline pb_size_t CheckedSize(size_t size) { + HARD_ASSERT(size <= PB_SIZE_MAX, + "Size exceeds nanopb limits. Too many entries."); + return static_cast(size); +} + +} // namespace nanopb +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_NANOPB_UTIL_H_ diff --git a/Firestore/core/src/firebase/firestore/remote/grpc_root_certificate_finder_apple.mm b/Firestore/core/src/firebase/firestore/remote/grpc_root_certificate_finder_apple.mm index 4cde6ea75dd..b7e0db4b0f5 100644 --- a/Firestore/core/src/firebase/firestore/remote/grpc_root_certificate_finder_apple.mm +++ b/Firestore/core/src/firebase/firestore/remote/grpc_root_certificate_finder_apple.mm @@ -16,14 +16,15 @@ #include "Firestore/core/src/firebase/firestore/remote/grpc_root_certificate_finder.h" +#import + #include #include "Firestore/core/src/firebase/firestore/util/filesystem.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "Firestore/core/src/firebase/firestore/util/log.h" #include "Firestore/core/src/firebase/firestore/util/statusor.h" - -#import "Firestore/Source/Core/FSTFirestoreClient.h" +#include "absl/strings/str_cat.h" namespace firebase { namespace firestore { @@ -34,45 +35,106 @@ using util::StatusOr; using util::StringFormat; +namespace { + +/** + * Finds the roots.pem certificate file in the given resource bundle and logs + * the outcome. + * + * @param bundle The bundle to check. Can be a nested bundle in Resources or + * an app or framework bundle to look in directly. + * @param parent The parent bundle of the bundle to search. Used for logging. + */ +NSString* _Nullable FindCertFileInResourceBundle(NSBundle* _Nullable bundle, + NSBundle* _Nullable parent) { + if (!bundle) return nil; + + NSString* path = [bundle pathForResource:@"roots" ofType:@"pem"]; + if (util::LogIsDebugEnabled()) { + std::string message = + absl::StrCat("roots.pem ", path ? "found " : "not found ", "in bundle ", + util::MakeString([bundle bundleIdentifier])); + if (parent) { + absl::StrAppend(&message, " (in parent ", + util::MakeString([parent bundleIdentifier]), ")"); + } + LOG_DEBUG("%s", message); + } + + return path; +} + +/** + * Finds gRPCCertificates.bundle inside the given parent, if it exists. + * + * This function exists mostly to handle differences in platforms. + * On iOS, resources are nested directly within the top-level of the parent + * bundle, but on macOS this will actually be in Contents/Resources. + * + * @param parent A framework or app bundle to check. + * @return The nested gRPCCertificates.bundle if found, otherwise nil. + */ +NSBundle* _Nullable FindCertBundleInParent(NSBundle* _Nullable parent) { + if (!parent) return nil; + + NSString* path = [parent pathForResource:@"gRPCCertificates" + ofType:@"bundle"]; + if (!path) return nil; + + return [[NSBundle alloc] initWithPath:path]; +} + +NSBundle* _Nullable FindFirestoreFrameworkBundle() { + // Load FIRFirestore reflectively to avoid a circular reference at build time. + Class firestore_class = objc_getClass("FIRFirestore"); + if (!firestore_class) return nil; + + return [NSBundle bundleForClass:firestore_class]; +} + +/** + * Finds the path to the roots.pem certificates file, wherever it may be. + * + * Carthage users will find roots.pem inside gRPCCertificates.bundle in + * the main bundle. + * + * There have been enough variations and workarounds posted on this that + * this also accepts the roots.pem file outside gRPCCertificates.bundle. + */ NSString* FindPathToCertificatesFile() { - // Certificates file might be present in either the gRPC-C++ bundle or (for + // Certificates file might be present in either the gRPC-C++ framework or (for // some projects) in the main bundle. NSBundle* bundles[] = { - // Try to load certificates bundled by gRPC-C++. + // CocoaPods: try to load from the gRPC-C++ Framework. [NSBundle bundleWithIdentifier:@"org.cocoapods.grpcpp"], - // Users manually adding resources to the project may add the - // certificate to the main application bundle. Note that `mainBundle` is - // nil for unit tests of library projects, so it cannot fully substitute - // for checking the framework bundle. + + // Carthage: try to load from the FirebaseFirestore.framework + FindFirestoreFrameworkBundle(), + + // Carthage and manual projects: users manually adding resources to the + // project may add the certificate to the main application bundle. Note + // that `mainBundle` is nil for unit tests of library projects. [NSBundle mainBundle], }; - // search for the roots.pem file in each of these resource locations - NSString* possibleResources[] = { - @"gRPCCertificates.bundle/roots", - @"roots", - }; + NSString* path = nil; - for (NSBundle* bundle : bundles) { - if (!bundle) { - continue; - } + for (NSBundle* parent : bundles) { + if (!parent) continue; - for (NSString* resource : possibleResources) { - NSString* path = [bundle pathForResource:resource ofType:@"pem"]; - if (path) { - LOG_DEBUG("%s.pem found in bundle %s", resource, - [bundle bundleIdentifier]); - return path; - } else { - LOG_DEBUG("%s.pem not found in bundle %s", resource, - [bundle bundleIdentifier]); - } - } + NSBundle* certs_bundle = FindCertBundleInParent(parent); + path = FindCertFileInResourceBundle(certs_bundle, parent); + if (path) break; + + path = FindCertFileInResourceBundle(parent, nil); + if (path) break; } - return nil; + + return path; } +} // namespace + std::string LoadGrpcRootCertificate() { NSString* path = FindPathToCertificatesFile(); HARD_ASSERT( diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.cc b/Firestore/core/src/firebase/firestore/remote/serializer.cc index a9c825047a6..cd0b9a15c4f 100644 --- a/Firestore/core/src/firebase/firestore/remote/serializer.cc +++ b/Firestore/core/src/firebase/firestore/remote/serializer.cc @@ -32,8 +32,10 @@ #include "Firestore/core/include/firebase/firestore/timestamp.h" #include "Firestore/core/src/firebase/firestore/model/document.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" #include "Firestore/core/src/firebase/firestore/model/no_document.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/nanopb/nanopb_util.h" #include "Firestore/core/src/firebase/firestore/nanopb/reader.h" #include "Firestore/core/src/firebase/firestore/nanopb/writer.h" #include "Firestore/core/src/firebase/firestore/timestamp_internal.h" @@ -66,13 +68,14 @@ using firebase::firestore::model::Precondition; using firebase::firestore::model::ResourcePath; using firebase::firestore::model::SetMutation; using firebase::firestore::model::SnapshotVersion; +using firebase::firestore::nanopb::CheckedSize; using firebase::firestore::nanopb::Reader; using firebase::firestore::nanopb::Writer; using firebase::firestore::util::Status; using firebase::firestore::util::StringFormat; pb_bytes_array_t* Serializer::EncodeString(const std::string& str) { - auto size = static_cast(str.size()); + pb_size_t size = CheckedSize(str.size()); auto result = static_cast(malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(size))); result->size = size; @@ -82,11 +85,12 @@ pb_bytes_array_t* Serializer::EncodeString(const std::string& str) { std::string Serializer::DecodeString(const pb_bytes_array_t* str) { if (str == nullptr) return ""; - return std::string{reinterpret_cast(str->bytes), str->size}; + size_t size = static_cast(str->size); + return std::string{reinterpret_cast(str->bytes), size}; } pb_bytes_array_t* Serializer::EncodeBytes(const std::vector& bytes) { - auto size = static_cast(bytes.size()); + pb_size_t size = CheckedSize(bytes.size()); auto result = static_cast(malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(size))); result->size = size; @@ -101,8 +105,8 @@ std::vector Serializer::DecodeBytes(const pb_bytes_array_t* bytes) { namespace { -ObjectValue::Map DecodeMapValue(Reader* reader, - const google_firestore_v1_MapValue& map_value); +FieldValue::Map DecodeMapValue(Reader* reader, + const google_firestore_v1_MapValue& map_value); // There's no f:f::model equivalent of StructuredQuery, so we'll create our // own struct for decoding. We could use nanopb's struct, but it's slightly @@ -119,7 +123,7 @@ struct StructuredQuery { // TODO(rsgowman): other fields }; -ObjectValue::Map::value_type DecodeFieldsEntry( +FieldValue::Map::value_type DecodeFieldsEntry( Reader* reader, const google_firestore_v1_Document_FieldsEntry& fields) { std::string key = Serializer::DecodeString(fields.key); FieldValue value = Serializer::DecodeFieldValue(reader, fields.value); @@ -130,33 +134,32 @@ ObjectValue::Map::value_type DecodeFieldsEntry( return {}; } - return ObjectValue::Map::value_type{std::move(key), std::move(value)}; + return FieldValue::Map::value_type{std::move(key), std::move(value)}; } -ObjectValue::Map DecodeFields( +FieldValue::Map DecodeFields( Reader* reader, size_t count, const google_firestore_v1_Document_FieldsEntry* fields) { - ObjectValue::Map result; + FieldValue::Map result; for (size_t i = 0; i < count; i++) { - ObjectValue::Map::value_type kv = DecodeFieldsEntry(reader, fields[i]); + FieldValue::Map::value_type kv = DecodeFieldsEntry(reader, fields[i]); result = result.insert(std::move(kv.first), std::move(kv.second)); } return result; } -google_firestore_v1_MapValue EncodeMapValue( - const ObjectValue::Map& object_value_map) { +google_firestore_v1_MapValue EncodeMapValue(const ObjectValue& object_value) { google_firestore_v1_MapValue result{}; - size_t count = object_value_map.size(); + pb_size_t count = CheckedSize(object_value.GetInternalValue().size()); - result.fields_count = static_cast(count); + result.fields_count = count; result.fields = MakeArray(count); int i = 0; - for (const auto& kv : object_value_map) { + for (const auto& kv : object_value.GetInternalValue()) { result.fields[i].key = Serializer::EncodeString(kv.first); result.fields[i].value = Serializer::EncodeFieldValue(kv.second); i++; @@ -165,9 +168,9 @@ google_firestore_v1_MapValue EncodeMapValue( return result; } -ObjectValue::Map DecodeMapValue(Reader* reader, - const google_firestore_v1_MapValue& map_value) { - ObjectValue::Map result; +FieldValue::Map DecodeMapValue(Reader* reader, + const google_firestore_v1_MapValue& map_value) { + FieldValue::Map result; for (size_t i = 0; i < map_value.fields_count; i++) { std::string key = Serializer::DecodeString(map_value.fields[i].key); @@ -286,39 +289,62 @@ google_firestore_v1_Value Serializer::EncodeFieldValue( case FieldValue::Type::Null: result.which_value_type = google_firestore_v1_Value_null_value_tag; result.null_value = google_protobuf_NullValue_NULL_VALUE; - break; + return result; case FieldValue::Type::Boolean: result.which_value_type = google_firestore_v1_Value_boolean_value_tag; result.boolean_value = field_value.boolean_value(); - break; + return result; case FieldValue::Type::Integer: result.which_value_type = google_firestore_v1_Value_integer_value_tag; result.integer_value = field_value.integer_value(); - break; + return result; - case FieldValue::Type::String: - result.which_value_type = google_firestore_v1_Value_string_value_tag; - result.string_value = EncodeString(field_value.string_value()); - break; + case FieldValue::Type::Double: + result.which_value_type = google_firestore_v1_Value_double_value_tag; + result.double_value = field_value.double_value(); + return result; case FieldValue::Type::Timestamp: result.which_value_type = google_firestore_v1_Value_timestamp_value_tag; result.timestamp_value = EncodeTimestamp(field_value.timestamp_value()); - break; + return result; - case FieldValue::Type::Object: - result.which_value_type = google_firestore_v1_Value_map_value_tag; - result.map_value = - EncodeMapValue(field_value.object_value().internal_value); - break; + case FieldValue::Type::ServerTimestamp: + // TODO(rsgowman): Implement + abort(); - default: - // TODO(rsgowman): implement the other types + case FieldValue::Type::String: + result.which_value_type = google_firestore_v1_Value_string_value_tag; + result.string_value = EncodeString(field_value.string_value()); + return result; + + case FieldValue::Type::Blob: + result.which_value_type = google_firestore_v1_Value_bytes_value_tag; + result.bytes_value = EncodeBytes(field_value.blob_value()); + return result; + + case FieldValue::Type::Reference: + // TODO(rsgowman): Implement abort(); + + case FieldValue::Type::GeoPoint: + result.which_value_type = google_firestore_v1_Value_geo_point_value_tag; + result.geo_point_value = EncodeGeoPoint(field_value.geo_point_value()); + return result; + + case FieldValue::Type::Array: + result.which_value_type = google_firestore_v1_Value_array_value_tag; + result.array_value = EncodeArray(field_value.array_value()); + return result; + + case FieldValue::Type::Object: + result.which_value_type = google_firestore_v1_Value_map_value_tag; + result.map_value = EncodeMapValue(ObjectValue(field_value)); + return result; } - return result; + UNREACHABLE(); } FieldValue Serializer::DecodeFieldValue(Reader* reader, @@ -343,27 +369,38 @@ FieldValue Serializer::DecodeFieldValue(Reader* reader, case google_firestore_v1_Value_integer_value_tag: return FieldValue::FromInteger(msg.integer_value); - case google_firestore_v1_Value_string_value_tag: - return FieldValue::FromString(DecodeString(msg.string_value)); + case google_firestore_v1_Value_double_value_tag: + return FieldValue::FromDouble(msg.double_value); case google_firestore_v1_Value_timestamp_value_tag: { return FieldValue::FromTimestamp( DecodeTimestamp(reader, msg.timestamp_value)); } - case google_firestore_v1_Value_map_value_tag: { - return FieldValue::FromMap(DecodeMapValue(reader, msg.map_value)); + case google_firestore_v1_Value_string_value_tag: + return FieldValue::FromString(DecodeString(msg.string_value)); + + case google_firestore_v1_Value_bytes_value_tag: { + std::vector bytes = DecodeBytes(msg.bytes_value); + return FieldValue::FromBlob(bytes.data(), bytes.size()); } - case google_firestore_v1_Value_double_value_tag: - case google_firestore_v1_Value_bytes_value_tag: case google_firestore_v1_Value_reference_value_tag: - case google_firestore_v1_Value_geo_point_value_tag: - case google_firestore_v1_Value_array_value_tag: // TODO(b/74243929): Implement remaining types. HARD_FAIL("Unhandled message field number (tag): %i.", msg.which_value_type); + case google_firestore_v1_Value_geo_point_value_tag: + return FieldValue::FromGeoPoint( + DecodeGeoPoint(reader, msg.geo_point_value)); + + case google_firestore_v1_Value_array_value_tag: + return FieldValue::FromArray(DecodeArray(reader, msg.array_value)); + + case google_firestore_v1_Value_map_value_tag: { + return FieldValue::FromMap(DecodeMapValue(reader, msg.map_value)); + } + default: reader->Fail(StringFormat("Invalid type while decoding FieldValue: %s", msg.which_value_type)); @@ -416,11 +453,11 @@ google_firestore_v1_Document Serializer::EncodeDocument( result.name = EncodeString(EncodeKey(key)); // Encode Document.fields (unless it's empty) - size_t count = object_value.internal_value.size(); - result.fields_count = static_cast(count); + pb_size_t count = CheckedSize(object_value.GetInternalValue().size()); + result.fields_count = count; result.fields = MakeArray(count); int i = 0; - for (const auto& kv : object_value.internal_value) { + for (const auto& kv : object_value.GetInternalValue()) { result.fields[i].key = EncodeString(kv.first); result.fields[i].value = EncodeFieldValue(kv.second); i++; @@ -457,7 +494,7 @@ std::unique_ptr Serializer::DecodeFoundDocument( "Tried to deserialize a found document from a missing document."); DocumentKey key = DecodeKey(reader, DecodeString(response.found.name)); - ObjectValue::Map value = + FieldValue::Map value = DecodeFields(reader, response.found.fields_count, response.found.fields); SnapshotVersion version = DecodeSnapshotVersion(reader, response.found.update_time); @@ -466,7 +503,7 @@ std::unique_ptr Serializer::DecodeFoundDocument( reader->Fail("Got a document response with no snapshot version"); } - return absl::make_unique(FieldValue::FromMap(std::move(value)), + return absl::make_unique(ObjectValue::FromMap(std::move(value)), std::move(key), std::move(version), DocumentState::kSynced); } @@ -492,12 +529,12 @@ std::unique_ptr Serializer::DecodeMissingDocument( std::unique_ptr Serializer::DecodeDocument( Reader* reader, const google_firestore_v1_Document& proto) const { - ObjectValue::Map fields_internal = + FieldValue::Map fields_internal = DecodeFields(reader, proto.fields_count, proto.fields); SnapshotVersion version = DecodeSnapshotVersion(reader, proto.update_time); return absl::make_unique( - FieldValue::FromMap(std::move(fields_internal)), + ObjectValue::FromMap(std::move(fields_internal)), DecodeKey(reader, DecodeString(proto.name)), std::move(version), DocumentState::kSynced); } @@ -514,16 +551,14 @@ google_firestore_v1_Write Serializer::EncodeMutation( case Mutation::Type::kSet: { result.which_operation = google_firestore_v1_Write_update_tag; result.update = EncodeDocument( - mutation.key(), - static_cast(mutation).value().object_value()); + mutation.key(), static_cast(mutation).value()); return result; } case Mutation::Type::kPatch: { result.which_operation = google_firestore_v1_Write_update_tag; auto patch_mutation = static_cast(mutation); - result.update = - EncodeDocument(mutation.key(), patch_mutation.value().object_value()); + result.update = EncodeDocument(mutation.key(), patch_mutation.value()); result.update_mask = EncodeDocumentMask(patch_mutation.mask()); return result; } @@ -565,7 +600,7 @@ std::unique_ptr Serializer::DecodeMutation( switch (mutation.which_operation) { case google_firestore_v1_Write_update_tag: { DocumentKey key = DecodeKey(reader, DecodeString(mutation.update.name)); - FieldValue value = FieldValue::FromMap(DecodeFields( + ObjectValue value = ObjectValue::FromMap(DecodeFields( reader, mutation.update.fields_count, mutation.update.fields)); FieldMask mask = DecodeDocumentMask(mutation.update_mask); if (mask.size() > 0) { @@ -669,10 +704,8 @@ google_firestore_v1_DocumentMask Serializer::EncodeDocumentMask( const FieldMask& mask) { google_firestore_v1_DocumentMask result{}; - size_t count = mask.size(); - HARD_ASSERT(count <= std::numeric_limits::max(), - "Unable to encode specified document mask. Too many fields."); - result.field_paths_count = static_cast(count); + pb_size_t count = CheckedSize(mask.size()); + result.field_paths_count = count; result.field_paths = MakeArray(count); int i = 0; @@ -701,6 +734,7 @@ google_firestore_v1_Target_QueryTarget Serializer::EncodeQueryTarget( // Dissect the path into parent, collection_id and optional key filter. std::string collection_id; + // TODO(rsgowman): Port Collection Group Queries logic. if (query.path().empty()) { result.parent = EncodeString(EncodeQueryPath(ResourcePath::Empty())); } else { @@ -715,8 +749,8 @@ google_firestore_v1_Target_QueryTarget Serializer::EncodeQueryTarget( google_firestore_v1_Target_QueryTarget_structured_query_tag; if (!collection_id.empty()) { - size_t count = 1; - result.structured_query.from_count = static_cast(count); + pb_size_t count = 1; + result.structured_query.from_count = count; result.structured_query.from = MakeArray( count); @@ -818,7 +852,7 @@ Timestamp Serializer::DecodeTimestamp( reader->Fail( "Invalid message: timestamp beyond the earliest supported date"); } else if (TimestampInternal::Max().seconds() < timestamp_proto.seconds) { - reader->Fail("Invalid message: timestamp behond the latest supported date"); + reader->Fail("Invalid message: timestamp beyond the latest supported date"); } else if (timestamp_proto.nanos < 0 || timestamp_proto.nanos > 999999999) { reader->Fail( "Invalid message: timestamp nanos must be between 0 and 999999999"); @@ -828,6 +862,64 @@ Timestamp Serializer::DecodeTimestamp( return Timestamp{timestamp_proto.seconds, timestamp_proto.nanos}; } +/* static */ +google_type_LatLng Serializer::EncodeGeoPoint(const GeoPoint& geo_point_value) { + google_type_LatLng result{}; + result.latitude = geo_point_value.latitude(); + result.longitude = geo_point_value.longitude(); + return result; +} + +/* static */ +GeoPoint Serializer::DecodeGeoPoint(nanopb::Reader* reader, + const google_type_LatLng& latlng_proto) { + // The GeoPoint ctor will assert if we provide values outside the valid range. + // However, since we're decoding, a single corrupt byte could cause this to + // occur, so we'll verify the ranges before passing them in since we'd rather + // not abort in these situations. + double latitude = latlng_proto.latitude; + double longitude = latlng_proto.longitude; + if (std::isnan(latitude) || latitude < -90 || 90 < latitude) { + reader->Fail("Invalid message: Latitude must be in the range of [-90, 90]"); + } else if (std::isnan(longitude) || longitude < -180 || 180 < longitude) { + reader->Fail( + "Invalid message: Latitude must be in the range of [-180, 180]"); + } + + if (!reader->status().ok()) return GeoPoint(); + return GeoPoint(latitude, longitude); +} + +/* static */ +google_firestore_v1_ArrayValue Serializer::EncodeArray( + const std::vector& array_value) { + google_firestore_v1_ArrayValue result{}; + + pb_size_t count = CheckedSize(array_value.size()); + result.values_count = count; + result.values = MakeArray(count); + + size_t i = 0; + for (const FieldValue& fv : array_value) { + result.values[i++] = EncodeFieldValue(fv); + } + + return result; +} + +/* static */ +std::vector Serializer::DecodeArray( + nanopb::Reader* reader, const google_firestore_v1_ArrayValue& array_proto) { + std::vector result; + result.reserve(array_proto.values_count); + + for (size_t i = 0; i < array_proto.values_count; i++) { + result.push_back(DecodeFieldValue(reader, array_proto.values[i])); + } + + return result; +} + } // namespace remote } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.h b/Firestore/core/src/firebase/firestore/remote/serializer.h index 086db7d191f..ae011b3defa 100644 --- a/Firestore/core/src/firebase/firestore/remote/serializer.h +++ b/Firestore/core/src/firebase/firestore/remote/serializer.h @@ -25,6 +25,7 @@ #include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/Protos/nanopb/google/firestore/v1/firestore.nanopb.h" +#include "Firestore/Protos/nanopb/google/type/latlng.nanopb.h" #include "Firestore/core/src/firebase/firestore/core/query.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/document.h" @@ -52,7 +53,7 @@ class LocalSerializer; namespace remote { template -T* MakeArray(size_t count) { +T* MakeArray(pb_size_t count) { return reinterpret_cast(calloc(count, sizeof(T))); } @@ -194,11 +195,6 @@ class Serializer { std::unique_ptr DecodeDocument( nanopb::Reader* reader, const google_firestore_v1_Document& proto) const; - static void EncodeObjectMap(const model::ObjectValue::Map& object_value_map, - uint32_t map_tag, - uint32_t key_tag, - uint32_t value_tag); - static google_protobuf_Timestamp EncodeVersion( const model::SnapshotVersion& version); @@ -215,6 +211,16 @@ class Serializer { nanopb::Reader* reader, const google_firestore_v1_Target_QueryTarget& proto); + static google_type_LatLng EncodeGeoPoint(const GeoPoint& geo_point_value); + static GeoPoint DecodeGeoPoint(nanopb::Reader* reader, + const google_type_LatLng& latlng_proto); + + static google_firestore_v1_ArrayValue EncodeArray( + const std::vector& array_value); + static std::vector DecodeArray( + nanopb::Reader* reader, + const google_firestore_v1_ArrayValue& array_proto); + private: std::unique_ptr DecodeFoundDocument( nanopb::Reader* reader, @@ -223,10 +229,6 @@ class Serializer { nanopb::Reader* reader, const google_firestore_v1_BatchGetDocumentsResponse& response) const; - static void EncodeFieldsEntry(const model::ObjectValue::Map::value_type& kv, - uint32_t key_tag, - uint32_t value_tag); - std::string EncodeQueryPath(const model::ResourcePath& path) const; const model::DatabaseId& database_id_; diff --git a/Firestore/core/src/firebase/firestore/timestamp.cc b/Firestore/core/src/firebase/firestore/timestamp.cc index c35fca10ed1..50ddec65ad3 100644 --- a/Firestore/core/src/firebase/firestore/timestamp.cc +++ b/Firestore/core/src/firebase/firestore/timestamp.cc @@ -16,7 +16,10 @@ #include "Firestore/core/include/firebase/firestore/timestamp.h" +#include + #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "absl/strings/str_cat.h" namespace firebase { @@ -84,8 +87,12 @@ Timestamp Timestamp::FromTimePoint( #endif // !defined(_STLPORT_VERSION) std::string Timestamp::ToString() const { - return std::string("Timestamp(seconds=") + std::to_string(seconds_) + - ", nanoseconds=" + std::to_string(nanoseconds_) + ")"; + return absl::StrCat("Timestamp(seconds=", seconds_, + ", nanoseconds=", nanoseconds_, ")"); +} + +std::ostream& operator<<(std::ostream& out, const Timestamp& timestamp) { + return out << timestamp.ToString(); } void Timestamp::ValidateBounds() const { diff --git a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt index 3bac4f2757f..e7460300bce 100644 --- a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt @@ -245,8 +245,12 @@ cc_library( comparison.cc comparison.h config.h + delayed_constructor.h + error_apple.h + error_apple.mm hashing.h iterator_adaptors.h + objc_compatibility.h ordered_code.cc ordered_code.h range.h diff --git a/Firestore/core/src/firebase/firestore/util/delayed_constructor.h b/Firestore/core/src/firebase/firestore/util/delayed_constructor.h new file mode 100644 index 00000000000..c45b1868476 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/util/delayed_constructor.h @@ -0,0 +1,132 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_DELAYED_CONSTRUCTOR_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_DELAYED_CONSTRUCTOR_H_ + +#include +#include + +namespace firebase { +namespace firestore { +namespace util { + +// DelayedConstructor is a wrapper around an object of type T that +// +// * stores the object of type T inline inside DelayedConstructor; +// * initially does not call T's constructor, leaving storage uninitialized; +// * calls the constructor when you call Init(); +// * provides access to the object of type T like a pointer via ->, *, and +// get(); and +// * calls T's destructor as usual. +// +// This is useful for embedding objects of type T inside Objective-C objects +// when T has no default constructor. +// +// Objective-C separates allocation from initialization which is different from +// the way C++ does it. A C++ object embedded in an Objective-C object is +// normally default constructed then assigned a value later. This doesn't work +// for classes that have no default constructor. +// +// DelayedConstructor does not count or otherwise check that Init is only +// called once. For best results call Init() from the Objective-C class's +// designated initializer. +// +// Note that DelayedConstructor makes no guarantees about the state of the +// storage backing it before Init() is called. However, Objective-C objects are +// zero filled during allocation, so as a member of an Objective-C object, the +// default state will be zero-filled. +// +// Normally this doesn't matter, but DelayedConstructor unconditionally invokes +// T's destructor, even if you don't call Init(). This may cause problems in +// Objective-C classes where the initializer is designed to return an instance +// other than self. It's best to avoid such instance switching techniques in +// combination with DelayedConstructor, but it is possible: either ensure that +// T's destructor handles the zero-filled case correctly, or call Init() before +// switching instances. +template +class DelayedConstructor { + public: + typedef T element_type; + + DelayedConstructor() = default; + + /** + * Forwards arguments to the T's constructor: calls T(args...). + * + * This overload is disabled when it might collide with copy/move. + */ + template ::type...), + void(DelayedConstructor)>::value, + int>::type = 0> + void Init(Ts&&... args) { + new (&space_.value) T(std::forward(args)...); + } + + /** + * Forwards copy and move construction for T. + */ + void Init(const T& x) { + new (&space_.value) T(x); + } + void Init(T&& x) { + new (&space_.value) T(std::move(x)); + } + + // No copying. + DelayedConstructor(const DelayedConstructor&) = delete; + DelayedConstructor& operator=(const DelayedConstructor&) = delete; + + // Pretend to be a smart pointer to T. + T& operator*() { + return *get(); + } + T* operator->() { + return get(); + } + T* get() { + return &space_.value; + } + const T& operator*() const { + return *get(); + } + const T* operator->() const { + return get(); + } + const T* get() const { + return &space_.value; + } + + private: + union Space { + /** Default constructor does nothing. */ + Space() { + } + ~Space() { + value.~T(); + } + char empty; + T value; + } space_; +}; + +} // namespace util +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_DELAYED_CONSTRUCTOR_H_ diff --git a/Firestore/core/src/firebase/firestore/util/error_apple.h b/Firestore/core/src/firebase/firestore/util/error_apple.h index a091840dcaf..88254bc4a62 100644 --- a/Firestore/core/src/firebase/firestore/util/error_apple.h +++ b/Firestore/core/src/firebase/firestore/util/error_apple.h @@ -22,31 +22,43 @@ #import -#import // for FIRFirestoreErrorDomain - #include "Firestore/core/include/firebase/firestore/firestore_errors.h" #include "Firestore/core/src/firebase/firestore/util/status.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" #include "absl/strings/string_view.h" +// The Cloud Firestore error domain. Keep in sync with FIRFirestoreErrors.h. +// Exposed here to make it possible to build in CMake without bringing in the +// sources under Firestore/Source. +FOUNDATION_EXPORT NSString* const FIRFirestoreErrorDomain + NS_SWIFT_NAME(FirestoreErrorDomain); + namespace firebase { namespace firestore { namespace util { // Translates a set of error_code and error_msg to an NSError. inline NSError* MakeNSError(const int64_t error_code, - const absl::string_view error_msg) { + const absl::string_view error_msg, + NSError* cause = nil) { if (error_code == FirestoreErrorCode::Ok) { return nil; } - return [NSError - errorWithDomain:FIRFirestoreErrorDomain - code:static_cast(error_code) - userInfo:@{NSLocalizedDescriptionKey : WrapNSString(error_msg)}]; + + NSMutableDictionary* user_info = + [NSMutableDictionary dictionary]; + user_info[NSLocalizedDescriptionKey] = WrapNSString(error_msg); + if (cause) { + user_info[NSUnderlyingErrorKey] = cause; + } + + return [NSError errorWithDomain:FIRFirestoreErrorDomain + code:static_cast(error_code) + userInfo:user_info]; } inline NSError* MakeNSError(const util::Status& status) { - return MakeNSError(status.code(), status.error_message()); + return status.ToNSError(); } inline Status MakeStatus(NSError* error) { diff --git a/Firestore/core/src/firebase/firestore/util/error_apple.mm b/Firestore/core/src/firebase/firestore/util/error_apple.mm new file mode 100644 index 00000000000..b558b6062a4 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/util/error_apple.mm @@ -0,0 +1,22 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/util/error_apple.h" + +// NB: This is also defined in Firestore/Source/Public/FIRFirestoreErrors.h +// NOLINTNEXTLINE: public constant +FOUNDATION_EXPORT NSString* const FIRFirestoreErrorDomain = + @"FIRFirestoreErrorDomain"; diff --git a/Firestore/core/src/firebase/firestore/util/log.h b/Firestore/core/src/firebase/firestore/util/log.h index 248d434ac4e..c02af00080e 100644 --- a/Firestore/core/src/firebase/firestore/util/log.h +++ b/Firestore/core/src/firebase/firestore/util/log.h @@ -31,6 +31,8 @@ enum LogLevel { kLogLevelDebug, // Warning Log Level kLogLevelWarning, + // Error Log Level + kLogLevelError, }; // Log a message if kLogLevelDebug is enabled. Arguments are not evaluated if @@ -63,6 +65,21 @@ enum LogLevel { } \ } while (0) +// Log a message if kLogLevelError is enabled (it is by default). Arguments are +// not evaluated if logging is disabled. +// +// @param format A format string suitable for use with `util::StringFormat` +// @param ... C++ variadic arguments that match the format string. Not C +// varargs. +#define LOG_ERROR(...) \ + do { \ + namespace _util = firebase::firestore::util; \ + if (_util::LogIsLoggable(_util::kLogLevelError)) { \ + std::string _message = _util::StringFormat(__VA_ARGS__); \ + _util::LogMessage(_util::kLogLevelError, _message); \ + } \ + } while (0) + // Tests to see if the given log level is loggable. bool LogIsLoggable(LogLevel level); diff --git a/Firestore/core/src/firebase/firestore/util/log_apple.mm b/Firestore/core/src/firebase/firestore/util/log_apple.mm index e173b476904..808dac92449 100644 --- a/Firestore/core/src/firebase/firestore/util/log_apple.mm +++ b/Firestore/core/src/firebase/firestore/util/log_apple.mm @@ -32,6 +32,8 @@ namespace { +const FIRLoggerService kFIRLoggerFirestore = @"[Firebase/Firestore]"; + // Translates a C++ LogLevel to the equivalent Objective-C FIRLoggerLevel FIRLoggerLevel ToFIRLoggerLevel(LogLevel level) { switch (level) { @@ -39,6 +41,8 @@ FIRLoggerLevel ToFIRLoggerLevel(LogLevel level) { return FIRLoggerLevelDebug; case kLogLevelWarning: return FIRLoggerLevelWarning; + case kLogLevelError: + return FIRLoggerLevelError; default: // Unsupported log level. FIRSetLoggerLevel will deal with it. return static_cast(-1); @@ -64,7 +68,7 @@ void LogSetLevel(LogLevel level) { } bool LogIsLoggable(LogLevel level) { - return FIRIsLoggableLevel(ToFIRLoggerLevel(level), NO); + return FIRIsLoggableLevel(ToFIRLoggerLevel(level), false); } void LogMessage(LogLevel level, const std::string& message) { diff --git a/Firestore/core/src/firebase/firestore/util/log_stdio.cc b/Firestore/core/src/firebase/firestore/util/log_stdio.cc index 6fff8850230..91af4b5d6dc 100644 --- a/Firestore/core/src/firebase/firestore/util/log_stdio.cc +++ b/Firestore/core/src/firebase/firestore/util/log_stdio.cc @@ -49,6 +49,9 @@ void LogMessage(LogLevel log_level, const std::string& message) { case kLogLevelWarning: level_word = "WARNING"; break; + case kLogLevelError: + level_word = "ERROR"; + break; default: UNREACHABLE(); break; diff --git a/Firestore/core/src/firebase/firestore/util/objc_compatibility.h b/Firestore/core/src/firebase/firestore/util/objc_compatibility.h index 52c79e5d0cd..5745d77664a 100644 --- a/Firestore/core/src/firebase/firestore/util/objc_compatibility.h +++ b/Firestore/core/src/firebase/firestore/util/objc_compatibility.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "Firestore/core/src/firebase/firestore/util/string_apple.h" #include "Firestore/core/src/firebase/firestore/util/to_string.h" @@ -73,6 +74,43 @@ bool Equals(const T& lhs, const T& rhs) { [](Ptr o1, Ptr o2) { return Equals(o1, o2); }); } +/** + * A function object that implements equality for an Objective-C pointer by + * delegating to -isEqual:. This is useful for using Objective-C objects as + * keys in STL associative containers. + */ +template ::value>> +class EqualTo { + public: + bool operator()(T lhs, T rhs) const { + return [lhs isEqual:rhs]; + } +}; + +/** + * A function object that implements STL-compatible hash code for an Objective-C + * pointer by delegating to -hash. This is useful for using Objective-C objects + * as keys in std::unordered_map. + */ +template ::value>> +class Hash { + public: + size_t operator()(T value) const { + return static_cast([value hash]); + } +}; + +/** + * The equivalent of std::unordered_map, where the Key type is an Objective-C + * class. + */ +template ::value>> +using unordered_map = std::unordered_map, EqualTo>; + /** * Creates a debug description of the given `value` by calling `ToString` on it, * converting the result to an `NSString`. Exists mainly to simplify writing diff --git a/Firestore/core/src/firebase/firestore/util/status.cc b/Firestore/core/src/firebase/firestore/util/status.cc index 838d9fc605c..6f36cafea21 100644 --- a/Firestore/core/src/firebase/firestore/util/status.cc +++ b/Firestore/core/src/firebase/firestore/util/status.cc @@ -16,6 +16,9 @@ #include "Firestore/core/src/firebase/firestore/util/status.h" +#include +#include + #include "Firestore/core/src/firebase/firestore/util/string_format.h" #include "absl/memory/memory.h" @@ -47,6 +50,22 @@ Status& Status::CausedBy(const Status& cause) { } absl::StrAppend(&state_->msg, ": ", cause.error_message()); + + // If this Status has no accompanying PlatformError but the cause does, create + // an PlatformError for this Status ahead of time to preserve the causal chain + // that Status doesn't otherwise support. + if (state_->platform_error == nullptr && + cause.state_->platform_error != nullptr) { + state_->platform_error = + cause.state_->platform_error->WrapWith(code(), error_message()); + } + + return *this; +} + +Status& Status::WithPlatformError(std::unique_ptr error) { + HARD_ASSERT(!ok(), "Platform errors should not be applied to Status::OK()"); + state_->platform_error = std::move(error); return *this; } @@ -127,6 +146,11 @@ std::string Status::ToString() const { } } +std::ostream& operator<<(std::ostream& out, const Status& status) { + out << status.ToString(); + return out; +} + void Status::IgnoreError() const { // no-op } diff --git a/Firestore/core/src/firebase/firestore/util/status.h b/Firestore/core/src/firebase/firestore/util/status.h index 2d1e32904c6..3937d6c897c 100644 --- a/Firestore/core/src/firebase/firestore/util/status.h +++ b/Firestore/core/src/firebase/firestore/util/status.h @@ -35,6 +35,8 @@ namespace firebase { namespace firestore { namespace util { +class PlatformError; + /// Denotes success or failure of a call. class ABSL_MUST_USE_RESULT Status { public: @@ -63,6 +65,8 @@ class ABSL_MUST_USE_RESULT Status { #if defined(__OBJC__) static Status FromNSError(NSError* error); + + NSError* ToNSError() const; #endif // defined(__OBJC__) /// Returns true iff the status indicates success. @@ -97,9 +101,12 @@ class ABSL_MUST_USE_RESULT Status { /// \return *this Status& CausedBy(const Status& cause); + Status& WithPlatformError(std::unique_ptr error); + /// \brief Return a string representation of this status suitable for /// printing. Returns the string `"OK"` for success. std::string ToString() const; + friend std::ostream& operator<<(std::ostream& out, const Status& status); // Ignores any errors. This method does nothing except potentially suppress // complaints from any tools that are checking that errors are not dropped on @@ -109,20 +116,51 @@ class ABSL_MUST_USE_RESULT Status { private: static const std::string& empty_string(); struct State { + State() = default; + State(const State& other); + FirestoreErrorCode code; std::string msg; + + // An additional platform-specific error representation that was used to + // generate this Status. The PlatformError does not meaningfully contribute + // to the identity of this Status: it exists to allow tunneling e.g. + // NSError* to Status and back to NSError* losslessly. + std::unique_ptr platform_error; }; - // OK status has a `NULL` state_. Otherwise, `state_` points to + // OK status has a `nullptr` state_. Otherwise, `state_` points to // a `State` structure containing the error code and message(s) std::unique_ptr state_; void SlowCopyFrom(const State* src); }; +class PlatformError { + public: + virtual ~PlatformError() { + } + + virtual std::unique_ptr Copy() = 0; + + /** + * Creates a new PlatformError with the given code and message, whose cause is + * this PlatformError. + */ + virtual std::unique_ptr WrapWith(FirestoreErrorCode code, + std::string message) = 0; +}; + inline Status::Status(const Status& s) : state_((s.state_ == nullptr) ? nullptr : new State(*s.state_)) { } +inline Status::State::State(const State& s) + : code(s.code), + msg(s.msg), + platform_error((s.platform_error == nullptr) ? nullptr + : s.platform_error->Copy()) { +} + inline void Status::operator=(const Status& s) { // The following condition catches both aliasing (when this == &s), // and the common case where both s and *this are ok. diff --git a/Firestore/core/src/firebase/firestore/util/status_apple.mm b/Firestore/core/src/firebase/firestore/util/status_apple.mm index f0bf7a6809f..d248f6721da 100644 --- a/Firestore/core/src/firebase/firestore/util/status_apple.mm +++ b/Firestore/core/src/firebase/firestore/util/status_apple.mm @@ -18,30 +18,79 @@ #if defined(__APPLE__) +#include "Firestore/core/src/firebase/firestore/util/error_apple.h" #include "Firestore/core/src/firebase/firestore/util/string_format.h" +#include "absl/memory/memory.h" namespace firebase { namespace firestore { namespace util { +class UnderlyingNSError : public PlatformError { + public: + explicit UnderlyingNSError(NSError* error) : error_(error) { + } + + static std::unique_ptr Create(NSError* error) { + return absl::make_unique(error); + } + + static NSError* Recover( + const std::unique_ptr& platform_error) { + if (platform_error == nullptr) { + return nil; + } + + return static_cast(platform_error.get())->error(); + } + + std::unique_ptr Copy() override { + return absl::make_unique(error_); + } + + std::unique_ptr WrapWith(FirestoreErrorCode code, + std::string message) override { + NSError* chain = MakeNSError(code, message, error_); + return Create(chain); + } + + NSError* error() const { + return error_; + } + + private: + NSError* error_; +}; + Status Status::FromNSError(NSError* error) { if (!error) { return Status::OK(); } - NSError* original = error; + auto original = UnderlyingNSError::Create(error); while (error) { if ([error.domain isEqualToString:NSPOSIXErrorDomain]) { return FromErrno(static_cast(error.code), - MakeString(original.localizedDescription)); + MakeString(original->error().localizedDescription)) + .WithPlatformError(std::move(original)); } error = error.userInfo[NSUnderlyingErrorKey]; } return Status{FirestoreErrorCode::Unknown, - StringFormat("Unknown error: %s", original)}; + StringFormat("Unknown error: %s", original->error())} + .WithPlatformError(std::move(original)); +} + +NSError* Status::ToNSError() const { + if (ok()) return nil; + + NSError* error = UnderlyingNSError::Recover(state_->platform_error); + if (error) return error; + + return MakeNSError(code(), error_message()); } } // namespace util diff --git a/Firestore/core/src/firebase/firestore/util/status_posix.cc b/Firestore/core/src/firebase/firestore/util/status_posix.cc index 79c651bfbb0..c40e6cbabfb 100644 --- a/Firestore/core/src/firebase/firestore/util/status_posix.cc +++ b/Firestore/core/src/firebase/firestore/util/status_posix.cc @@ -184,6 +184,10 @@ static FirestoreErrorCode CodeForErrno(int errno_code) { } Status Status::FromErrno(int errno_code, absl::string_view msg) { + if (errno_code == 0) { + return Status::OK(); + } + FirestoreErrorCode canonical_code = CodeForErrno(errno_code); return Status{canonical_code, util::StringFormat("%s (errno %s: %s)", msg, errno_code, diff --git a/Firestore/core/src/firebase/firestore/util/statusor_callback.h b/Firestore/core/src/firebase/firestore/util/statusor_callback.h new file mode 100644 index 00000000000..ff39b5df2ae --- /dev/null +++ b/Firestore/core/src/firebase/firestore/util/statusor_callback.h @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUSOR_CALLBACK_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUSOR_CALLBACK_H_ + +#include + +#include "Firestore/core/src/firebase/firestore/util/statusor.h" + +namespace firebase { +namespace firestore { +namespace util { + +template +using StatusOrCallback = std::function)>; + +} // namespace util +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STATUSOR_CALLBACK_H_ diff --git a/Firestore/core/src/firebase/firestore/util/string_apple.h b/Firestore/core/src/firebase/firestore/util/string_apple.h index 436d2ab2cb8..b6eb6f636ee 100644 --- a/Firestore/core/src/firebase/firestore/util/string_apple.h +++ b/Firestore/core/src/firebase/firestore/util/string_apple.h @@ -46,17 +46,17 @@ inline CFStringRef MakeCFString(absl::string_view contents) { #if defined(__OBJC__) // Translates a C string to the equivalent NSString without making a copy. -inline NSString* WrapNSStringNoCopy(const char* c_str) { +inline NSString* WrapNSStringNoCopy(const char* c_str, size_t size) { return [[NSString alloc] initWithBytesNoCopy:const_cast(static_cast(c_str)) - length:strlen(c_str) + length:size encoding:NSUTF8StringEncoding - freeWhenDone:NO]; + freeWhenDone:false]; } // Translates a string_view to the equivalent NSString without making a copy. inline NSString* WrapNSStringNoCopy(const absl::string_view str) { - return WrapNSStringNoCopy(str.data()); + return WrapNSStringNoCopy(str.data(), str.size()); } // Translates a string_view string to the equivalent NSString by making a copy. diff --git a/Firestore/core/test/firebase/firestore/FSTGoogleTestTests.mm b/Firestore/core/test/firebase/firestore/FSTGoogleTestTests.mm index bb2f8364909..6895f23923c 100644 --- a/Firestore/core/test/firebase/firestore/FSTGoogleTestTests.mm +++ b/Firestore/core/test/firebase/firestore/FSTGoogleTestTests.mm @@ -50,7 +50,7 @@ @interface GoogleTests : XCTestCase // If the user focuses on GoogleTests itself, this means force all C++ tests to // run. -BOOL forceAllTests = NO; +bool forceAllTests = false; /** * Loads this XCTest runner's configuration file and figures out which tests to @@ -216,7 +216,7 @@ void ReportTestResult(XCTestCase *self, SEL _cmd) { atLine:(part.line_number() > 0 ? part.line_number() : 0) - expected:YES]; + expected:true]; } } @@ -225,8 +225,9 @@ void ReportTestResult(XCTestCase *self, SEL _cmd) { * Each TestInfo (which represents an indivudal test method execution) is * translated into a method on the test case. * - * @param The testing::TestCase of interest to translate. - * @param A map of TestInfoKeys to testing::TestInfos, populated by this method. + * @param testCase The testing::TestCase of interest to translate. + * @param infoMap A map of TestInfoKeys to testing::TestInfos, populated by this + * method. * * @return A new Class that's a subclass of XCTestCase, that's been registered * with the Objective-C runtime. @@ -304,15 +305,15 @@ void RunGoogleTestTests() { // Convert XCTest's testToRun set to the equivalent --gtest_filter flag. // - // Note that we only set forceAllTests to YES if the user specifically focused - // on GoogleTests. This prevents XCTest double-counting test cases (and - // failures) when a user asks for all tests. + // Note that we only set forceAllTests to true if the user specifically + // focused on GoogleTests. This prevents XCTest double-counting test cases + // (and failures) when a user asks for all tests. NSSet *allTests = [NSSet setWithObject:masterTestCaseName]; NSSet *testsToRun = LoadXCTestConfigurationTestsToRun(); if (testsToRun) { if ([allTests isEqual:testsToRun]) { NSLog(@"Forcing all tests to run"); - forceAllTests = YES; + forceAllTests = true; } else { NSString *filters = CreateTestFiltersFromTestsToRun(testsToRun); NSLog(@"Using --gtest_filter=%@", filters); diff --git a/Firestore/core/test/firebase/firestore/core/query_test.cc b/Firestore/core/test/firebase/firestore/core/query_test.cc index a6c810ce74f..ed58236f9d1 100644 --- a/Firestore/core/test/firebase/firestore/core/query_test.cc +++ b/Firestore/core/test/firebase/firestore/core/query_test.cc @@ -36,9 +36,9 @@ using testutil::Doc; using testutil::Filter; TEST(QueryTest, MatchesBasedOnDocumentKey) { - Document doc1 = Doc("rooms/eros/messages/1"); - Document doc2 = Doc("rooms/eros/messages/2"); - Document doc3 = Doc("rooms/other/messages/1"); + Document doc1 = *Doc("rooms/eros/messages/1"); + Document doc2 = *Doc("rooms/eros/messages/2"); + Document doc3 = *Doc("rooms/other/messages/1"); Query query = Query::AtPath({"rooms", "eros", "messages", "1"}); EXPECT_TRUE(query.Matches(doc1)); @@ -47,10 +47,10 @@ TEST(QueryTest, MatchesBasedOnDocumentKey) { } TEST(QueryTest, MatchesShallowAncestorQuery) { - Document doc1 = Doc("rooms/eros/messages/1"); - Document doc1_meta = Doc("rooms/eros/messages/1/meta/1"); - Document doc2 = Doc("rooms/eros/messages/2"); - Document doc3 = Doc("rooms/other/messages/1"); + Document doc1 = *Doc("rooms/eros/messages/1"); + Document doc1_meta = *Doc("rooms/eros/messages/1/meta/1"); + Document doc2 = *Doc("rooms/eros/messages/2"); + Document doc3 = *Doc("rooms/other/messages/1"); Query query = Query::AtPath({"rooms", "eros", "messages"}); EXPECT_TRUE(query.Matches(doc1)); @@ -60,9 +60,9 @@ TEST(QueryTest, MatchesShallowAncestorQuery) { } TEST(QueryTest, EmptyFieldsAreAllowedForQueries) { - Document doc1 = Doc("rooms/eros/messages/1", 0, - {{"text", FieldValue::FromString("msg1")}}); - Document doc2 = Doc("rooms/eros/messages/2"); + Document doc1 = *Doc("rooms/eros/messages/1", 0, + {{"text", FieldValue::FromString("msg1")}}); + Document doc2 = *Doc("rooms/eros/messages/2"); Query query = Query::AtPath({"rooms", "eros", "messages"}) .Filter(Filter("text", "==", "msg1")); @@ -77,14 +77,14 @@ TEST(QueryTest, PrimitiveValueFilter) { .Filter(Filter("sort", "<=", 2)); Document doc1 = - Doc("collection/1", 0, {{"sort", FieldValue::FromInteger(1)}}); + *Doc("collection/1", 0, {{"sort", FieldValue::FromInteger(1)}}); Document doc2 = - Doc("collection/2", 0, {{"sort", FieldValue::FromInteger(2)}}); + *Doc("collection/2", 0, {{"sort", FieldValue::FromInteger(2)}}); Document doc3 = - Doc("collection/3", 0, {{"sort", FieldValue::FromInteger(3)}}); - Document doc4 = Doc("collection/4", 0, {{"sort", FieldValue::False()}}); + *Doc("collection/3", 0, {{"sort", FieldValue::FromInteger(3)}}); + Document doc4 = *Doc("collection/4", 0, {{"sort", FieldValue::False()}}); Document doc5 = - Doc("collection/5", 0, {{"sort", FieldValue::FromString("string")}}); + *Doc("collection/5", 0, {{"sort", FieldValue::FromString("string")}}); EXPECT_FALSE(query1.Matches(doc1)); EXPECT_TRUE(query1.Matches(doc2)); @@ -103,14 +103,14 @@ TEST(QueryTest, NanFilter) { Query query = Query::AtPath(ResourcePath::FromString("collection")) .Filter(Filter("sort", "==", NAN)); - Document doc1 = Doc("collection/1", 0, {{"sort", FieldValue::Nan()}}); + Document doc1 = *Doc("collection/1", 0, {{"sort", FieldValue::Nan()}}); Document doc2 = - Doc("collection/2", 0, {{"sort", FieldValue::FromInteger(2)}}); + *Doc("collection/2", 0, {{"sort", FieldValue::FromInteger(2)}}); Document doc3 = - Doc("collection/3", 0, {{"sort", FieldValue::FromDouble(3.1)}}); - Document doc4 = Doc("collection/4", 0, {{"sort", FieldValue::False()}}); + *Doc("collection/3", 0, {{"sort", FieldValue::FromDouble(3.1)}}); + Document doc4 = *Doc("collection/4", 0, {{"sort", FieldValue::False()}}); Document doc5 = - Doc("collection/5", 0, {{"sort", FieldValue::FromString("string")}}); + *Doc("collection/5", 0, {{"sort", FieldValue::FromString("string")}}); EXPECT_TRUE(query.Matches(doc1)); EXPECT_FALSE(query.Matches(doc2)); diff --git a/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc b/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc index acd06429a47..2c26c45b403 100644 --- a/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc +++ b/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc @@ -36,8 +36,6 @@ #include "Firestore/core/test/firebase/firestore/immutable/testing.h" #include "gtest/gtest.h" -using firebase::firestore::immutable::impl::SortedMapBase; - namespace firebase { namespace firestore { namespace immutable { diff --git a/Firestore/core/test/firebase/firestore/immutable/sorted_set_test.cc b/Firestore/core/test/firebase/firestore/immutable/sorted_set_test.cc index a4b337cb166..3239fdafe8b 100644 --- a/Firestore/core/test/firebase/firestore/immutable/sorted_set_test.cc +++ b/Firestore/core/test/firebase/firestore/immutable/sorted_set_test.cc @@ -21,13 +21,12 @@ #include "Firestore/core/test/firebase/firestore/immutable/testing.h" -using firebase::firestore::immutable::impl::SortedMapBase; -using SizeType = SortedMapBase::size_type; - namespace firebase { namespace firestore { namespace immutable { +using SizeType = SortedContainer::size_type; + template SortedSet ToSet(const std::vector& container) { SortedSet result; diff --git a/Firestore/core/test/firebase/firestore/local/CMakeLists.txt b/Firestore/core/test/firebase/firestore/local/CMakeLists.txt index 8a97514fa46..f178b2baed6 100644 --- a/Firestore/core/test/firebase/firestore/local/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/local/CMakeLists.txt @@ -26,7 +26,10 @@ endif() cc_test( firebase_firestore_local_test SOURCES + #index_manager_test.mm + #leveldb_index_manager_test.mm local_serializer_test.cc + #memory_index_manager_test.mm DEPENDS firebase_firestore_local firebase_firestore_model diff --git a/Firestore/core/test/firebase/firestore/local/index_manager_test.h b/Firestore/core/test/firebase/firestore/local/index_manager_test.h new file mode 100644 index 00000000000..5c51424625b --- /dev/null +++ b/Firestore/core/test/firebase/firestore/local/index_manager_test.h @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_LOCAL_INDEX_MANAGER_TEST_H_ +#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_LOCAL_INDEX_MANAGER_TEST_H_ + +#if !defined(__OBJC__) +#error "For now, this file must only be included by ObjC source files." +#endif // !defined(__OBJC__) + +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/local/index_manager.h" +#include "gtest/gtest.h" + +#import "Firestore/Source/Local/FSTPersistence.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace local { + +using FactoryFunc = id _Nonnull (*)(); + +class IndexManagerTest : public ::testing::TestWithParam { + public: + // `GetParam()` must return a factory function. + IndexManagerTest() : persistence{GetParam()()} { + } + + id persistence; + + virtual ~IndexManagerTest(); + + protected: + void AssertParents(const std::string &collection_id, + std::vector expected); +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +NS_ASSUME_NONNULL_END + +#endif // FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_LOCAL_INDEX_MANAGER_TEST_H_ diff --git a/Firestore/core/test/firebase/firestore/local/index_manager_test.mm b/Firestore/core/test/firebase/firestore/local/index_manager_test.mm new file mode 100644 index 00000000000..a3f88631ab1 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/local/index_manager_test.mm @@ -0,0 +1,75 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "Firestore/core/test/firebase/firestore/local/index_manager_test.h" + +#include "Firestore/core/src/firebase/firestore/local/index_manager.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace local { + +using model::ResourcePath; + +void IndexManagerTest::AssertParents(const std::string &collection_id, + std::vector expected) { + IndexManager *index_manager = persistence.indexManager; + std::vector actual_paths = + index_manager->GetCollectionParents(collection_id); + std::vector actual; + for (const ResourcePath &actual_path : actual_paths) { + actual.push_back(actual_path.CanonicalString()); + } + std::sort(expected.begin(), expected.end()); + std::sort(actual.begin(), actual.end()); + + SCOPED_TRACE("AssertParents(\"" + collection_id + "\", ...)"); + EXPECT_EQ(actual, expected); +} + +IndexManagerTest::~IndexManagerTest() { + [persistence shutdown]; +} + +TEST_P(IndexManagerTest, AddAndReadCollectionParentIndexEntries) { + IndexManager *index_manager = persistence.indexManager; + persistence.run("AddAndReadCollectionParentIndexEntries", [&]() { + index_manager->AddToCollectionParentIndex(ResourcePath{"messages"}); + index_manager->AddToCollectionParentIndex(ResourcePath{"messages"}); + index_manager->AddToCollectionParentIndex( + ResourcePath{"rooms", "foo", "messages"}); + index_manager->AddToCollectionParentIndex( + ResourcePath{"rooms", "bar", "messages"}); + index_manager->AddToCollectionParentIndex( + ResourcePath{"rooms", "foo", "messages2"}); + + AssertParents("messages", + std::vector{"", "rooms/bar", "rooms/foo"}); + AssertParents("messages2", std::vector{"rooms/foo"}); + AssertParents("messages3", std::vector{}); + }); +} + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/local/leveldb_index_manager_test.mm b/Firestore/core/test/firebase/firestore/local/leveldb_index_manager_test.mm new file mode 100644 index 00000000000..a68581b17b1 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/local/leveldb_index_manager_test.mm @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/firebase/firestore/local/index_manager_test.h" + +#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h" +#import "Firestore/Source/Local/FSTPersistence.h" + +#include "Firestore/core/src/firebase/firestore/local/leveldb_index_manager.h" +#include "absl/memory/memory.h" +#include "gtest/gtest.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace local { + +namespace { + +id PersistenceFactory() { + return static_cast>( + [FSTPersistenceTestHelpers levelDBPersistence]); +} + +} // namespace + +INSTANTIATE_TEST_CASE_P(LevelDbIndexManagerTest, + IndexManagerTest, + ::testing::Values(PersistenceFactory)); + +NS_ASSUME_NONNULL_END + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/local/leveldb_key_test.cc b/Firestore/core/test/firebase/firestore/local/leveldb_key_test.cc index c2b1ea622e8..c6843f76943 100644 --- a/Firestore/core/test/firebase/firestore/local/leveldb_key_test.cc +++ b/Firestore/core/test/firebase/firestore/local/leveldb_key_test.cc @@ -201,11 +201,11 @@ TEST(LevelDbDocumentMutationKeyTest, Description) { auto key = LevelDbDocumentMutationKey::KeyPrefix( "user1", testutil::Resource("foo/bar")); AssertExpectedKeyDescription( - "[document_mutation: user_id=user1 key=foo/bar incomplete key]", key); + "[document_mutation: user_id=user1 path=foo/bar incomplete key]", key); key = LevelDbDocumentMutationKey::Key("user1", testutil::Key("foo/bar"), 42); AssertExpectedKeyDescription( - "[document_mutation: user_id=user1 key=foo/bar batch_id=42]", key); + "[document_mutation: user_id=user1 path=foo/bar batch_id=42]", key); } TEST(LevelDbTargetGlobalKeyTest, EncodeDecodeCycle) { @@ -279,7 +279,7 @@ TEST(TargetDocumentKeyTest, Ordering) { TEST(TargetDocumentKeyTest, Description) { auto key = LevelDbTargetDocumentKey::Key(42, testutil::Key("foo/bar")); - ASSERT_EQ("[target_document: target_id=42 key=foo/bar]", DescribeKey(key)); + ASSERT_EQ("[target_document: target_id=42 path=foo/bar]", DescribeKey(key)); } TEST(DocumentTargetKeyTest, EncodeDecodeCycle) { @@ -294,7 +294,7 @@ TEST(DocumentTargetKeyTest, EncodeDecodeCycle) { TEST(DocumentTargetKeyTest, Description) { auto key = LevelDbDocumentTargetKey::Key(testutil::Key("foo/bar"), 42); - ASSERT_EQ("[document_target: key=foo/bar target_id=42]", DescribeKey(key)); + ASSERT_EQ("[document_target: path=foo/bar target_id=42]", DescribeKey(key)); } TEST(DocumentTargetKeyTest, Ordering) { @@ -352,7 +352,7 @@ TEST(RemoteDocumentKeyTest, EncodeDecodeCycle) { TEST(RemoteDocumentKeyTest, Description) { AssertExpectedKeyDescription( - "[remote_document: key=foo/bar/baz/quux]", + "[remote_document: path=foo/bar/baz/quux]", LevelDbRemoteDocumentKey::Key(testutil::Key("foo/bar/baz/quux"))); } diff --git a/Firestore/core/test/firebase/firestore/local/local_serializer_test.cc b/Firestore/core/test/firebase/firestore/local/local_serializer_test.cc index f7d739c461f..fa045134696 100644 --- a/Firestore/core/test/firebase/firestore/local/local_serializer_test.cc +++ b/Firestore/core/test/firebase/firestore/local/local_serializer_test.cc @@ -58,6 +58,7 @@ using model::MaybeDocument; using model::Mutation; using model::MutationBatch; using model::NoDocument; +using model::ObjectValue; using model::PatchMutation; using model::Precondition; using model::SetMutation; @@ -258,8 +259,8 @@ TEST_F(LocalSerializerTest, EncodesMutationBatch) { {"num", FieldValue::FromInteger(1)}}); std::unique_ptr patch = absl::make_unique( Key("bar/baz"), - FieldValue::FromMap({{"a", FieldValue::FromString("b")}, - {"num", FieldValue::FromInteger(1)}}), + ObjectValue::FromMap({{"a", FieldValue::FromString("b")}, + {"num", FieldValue::FromInteger(1)}}), FieldMask({FieldPath({"a"})}), Precondition::Exists(true)); std::unique_ptr del = testutil::DeleteMutation("baz/quux"); @@ -309,8 +310,8 @@ TEST_F(LocalSerializerTest, EncodesMutationBatch) { } TEST_F(LocalSerializerTest, EncodesDocumentAsMaybeDocument) { - Document doc = Doc("some/path", /*version=*/42, - {{"foo", FieldValue::FromString("bar")}}); + Document doc = *Doc("some/path", /*version=*/42, + {{"foo", FieldValue::FromString("bar")}}); ::firestore::client::MaybeDocument maybe_doc_proto; maybe_doc_proto.mutable_document()->set_name( @@ -326,7 +327,7 @@ TEST_F(LocalSerializerTest, EncodesDocumentAsMaybeDocument) { } TEST_F(LocalSerializerTest, EncodesNoDocumentAsMaybeDocument) { - NoDocument no_doc = DeletedDoc("some/path", /*version=*/42); + NoDocument no_doc = *DeletedDoc("some/path", /*version=*/42); ::firestore::client::MaybeDocument maybe_doc_proto; maybe_doc_proto.mutable_no_document()->set_name( @@ -338,7 +339,7 @@ TEST_F(LocalSerializerTest, EncodesNoDocumentAsMaybeDocument) { } TEST_F(LocalSerializerTest, EncodesUnknownDocumentAsMaybeDocument) { - UnknownDocument unknown_doc = UnknownDoc("some/path", /*version=*/42); + UnknownDocument unknown_doc = *UnknownDoc("some/path", /*version=*/42); ::firestore::client::MaybeDocument maybe_doc_proto; maybe_doc_proto.mutable_unknown_document()->set_name( diff --git a/Firestore/core/test/firebase/firestore/local/memory_index_manager_test.mm b/Firestore/core/test/firebase/firestore/local/memory_index_manager_test.mm new file mode 100644 index 00000000000..28039b37243 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/local/memory_index_manager_test.mm @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/firebase/firestore/local/index_manager_test.h" + +#include "Firestore/core/src/firebase/firestore/local/memory_index_manager.h" +#include "absl/memory/memory.h" +#include "gtest/gtest.h" + +#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h" +#import "Firestore/Source/Local/FSTPersistence.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace firebase { +namespace firestore { +namespace local { + +namespace { + +id PersistenceFactory() { + return static_cast>( + [FSTPersistenceTestHelpers lruMemoryPersistence]); +} + +} // namespace + +INSTANTIATE_TEST_CASE_P(MemoryIndexManagerTest, + IndexManagerTest, + ::testing::Values(PersistenceFactory)); + +NS_ASSUME_NONNULL_END + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/model/document_test.cc b/Firestore/core/test/firebase/firestore/model/document_test.cc index 0aaba50abbb..ae09b75825d 100644 --- a/Firestore/core/test/firebase/firestore/model/document_test.cc +++ b/Firestore/core/test/firebase/firestore/model/document_test.cc @@ -16,6 +16,7 @@ #include "Firestore/core/src/firebase/firestore/model/document.h" +#include "Firestore/core/src/firebase/firestore/model/field_value.h" #include "Firestore/core/src/firebase/firestore/model/unknown_document.h" #include "absl/strings/string_view.h" @@ -32,7 +33,7 @@ inline Document MakeDocument(const absl::string_view data, const Timestamp& timestamp, DocumentState document_state) { return Document( - FieldValue::FromMap({{"field", FieldValue::FromString(data.data())}}), + ObjectValue::FromMap({{"field", FieldValue::FromString(data.data())}}), DocumentKey::FromPathString(path.data()), SnapshotVersion(timestamp), document_state); } @@ -43,7 +44,7 @@ TEST(Document, Getter) { const Document& doc = MakeDocument("foo", "i/am/a/path", Timestamp(123, 456), DocumentState::kLocalMutations); EXPECT_EQ(MaybeDocument::Type::Document, doc.type()); - EXPECT_EQ(FieldValue::FromMap({{"field", FieldValue::FromString("foo")}}), + EXPECT_EQ(ObjectValue::FromMap({{"field", FieldValue::FromString("foo")}}), doc.data()); EXPECT_EQ(DocumentKey::FromPathString("i/am/a/path"), doc.key()); EXPECT_EQ(SnapshotVersion(Timestamp(123, 456)), doc.version()); @@ -74,11 +75,11 @@ TEST(Document, Comparison) { // Document and MaybeDocument will not equal. In particular, Document and // NoDocument will not equal, which I won't test here. - EXPECT_NE(Document(FieldValue(FieldValue::EmptyObject()), - DocumentKey::FromPathString("same/path"), - SnapshotVersion(Timestamp()), DocumentState::kSynced), - UnknownDocument(DocumentKey::FromPathString("same/path"), - SnapshotVersion(Timestamp()))); + EXPECT_NE( + Document(ObjectValue::Empty(), DocumentKey::FromPathString("same/path"), + SnapshotVersion(Timestamp()), DocumentState::kSynced), + UnknownDocument(DocumentKey::FromPathString("same/path"), + SnapshotVersion(Timestamp()))); } } // namespace model diff --git a/Firestore/core/test/firebase/firestore/model/field_value_test.cc b/Firestore/core/test/firebase/firestore/model/field_value_test.cc index fc7d0177a9d..b1d1f8b70b5 100644 --- a/Firestore/core/test/firebase/firestore/model/field_value_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_value_test.cc @@ -189,19 +189,16 @@ TEST(FieldValue, ArrayType) { } TEST(FieldValue, ObjectType) { - const FieldValue empty = FieldValue::FromMap(ObjectValue::Empty()); - ObjectValue::Map object{{"null", FieldValue::Null()}, - {"true", FieldValue::True()}, - {"false", FieldValue::False()}}; + const ObjectValue empty = ObjectValue::Empty(); + FieldValue::Map object{{"null", FieldValue::Null()}, + {"true", FieldValue::True()}, + {"false", FieldValue::False()}}; // copy the map - const FieldValue small = FieldValue::FromMap(object); - ObjectValue::Map another_object{{"null", FieldValue::Null()}, - {"true", FieldValue::False()}}; + const ObjectValue small = ObjectValue::FromMap(object); + FieldValue::Map another_object{{"null", FieldValue::Null()}, + {"true", FieldValue::False()}}; // move the array - const FieldValue large = FieldValue::FromMap(std::move(another_object)); - EXPECT_EQ(Type::Object, empty.type()); - EXPECT_EQ(Type::Object, small.type()); - EXPECT_EQ(Type::Object, large.type()); + const ObjectValue large = ObjectValue::FromMap(std::move(another_object)); EXPECT_TRUE(empty < small); EXPECT_FALSE(small < empty); EXPECT_FALSE(small < small); @@ -334,18 +331,18 @@ TEST(FieldValue, Copy) { clone = null_value; EXPECT_EQ(FieldValue::Null(), clone); - const FieldValue object_value = FieldValue::FromMap(ObjectValue::Map{ - {"true", FieldValue::True()}, {"false", FieldValue::False()}}); + const FieldValue object_value = FieldValue::FromMap( + {{"true", FieldValue::True()}, {"false", FieldValue::False()}}); clone = object_value; - EXPECT_EQ(FieldValue::FromMap(ObjectValue::Map{ - {"true", FieldValue::True()}, {"false", FieldValue::False()}}), + EXPECT_EQ(FieldValue::FromMap( + {{"true", FieldValue::True()}, {"false", FieldValue::False()}}), clone); - EXPECT_EQ(FieldValue::FromMap(ObjectValue::Map{ - {"true", FieldValue::True()}, {"false", FieldValue::False()}}), + EXPECT_EQ(FieldValue::FromMap( + {{"true", FieldValue::True()}, {"false", FieldValue::False()}}), object_value); clone = *&clone; - EXPECT_EQ(FieldValue::FromMap(ObjectValue::Map{ - {"true", FieldValue::True()}, {"false", FieldValue::False()}}), + EXPECT_EQ(FieldValue::FromMap( + {{"true", FieldValue::True()}, {"false", FieldValue::False()}}), clone); clone = null_value; EXPECT_EQ(FieldValue::Null(), clone); @@ -425,11 +422,11 @@ TEST(FieldValue, Move) { clone = FieldValue::Null(); EXPECT_EQ(FieldValue::Null(), clone); - FieldValue object_value = FieldValue::FromMap(ObjectValue::Map{ - {"true", FieldValue::True()}, {"false", FieldValue::False()}}); + FieldValue object_value = FieldValue::FromMap( + {{"true", FieldValue::True()}, {"false", FieldValue::False()}}); clone = std::move(object_value); - EXPECT_EQ(FieldValue::FromMap(ObjectValue::Map{ - {"true", FieldValue::True()}, {"false", FieldValue::False()}}), + EXPECT_EQ(FieldValue::FromMap( + {{"true", FieldValue::True()}, {"false", FieldValue::False()}}), clone); clone = FieldValue::Null(); EXPECT_EQ(FieldValue::Null(), clone); @@ -489,13 +486,13 @@ TEST(FieldValue, CompareWithOperator) { TEST(FieldValue, Set) { // Set a field in an object. - const FieldValue value = FieldValue::FromMap({ + const ObjectValue value = ObjectValue::FromMap({ {"a", FieldValue::FromString("A")}, {"b", FieldValue::FromMap({ {"ba", FieldValue::FromString("BA")}, })}, }); - const FieldValue expected = FieldValue::FromMap({ + const ObjectValue expected = ObjectValue::FromMap({ {"a", FieldValue::FromString("A")}, {"b", FieldValue::FromMap({ {"ba", FieldValue::FromString("BA")}, @@ -508,10 +505,10 @@ TEST(FieldValue, Set) { TEST(FieldValue, SetRecursive) { // Set a field in a new object. - const FieldValue value = FieldValue::FromMap({ + const ObjectValue value = ObjectValue::FromMap({ {"a", FieldValue::FromString("A")}, }); - const FieldValue expected = FieldValue::FromMap({ + const ObjectValue expected = ObjectValue::FromMap({ {"a", FieldValue::FromString("A")}, {"b", FieldValue::FromMap({ {"bb", FieldValue::FromString("BB")}, @@ -522,14 +519,14 @@ TEST(FieldValue, SetRecursive) { } TEST(FieldValue, Delete) { - const FieldValue value = FieldValue::FromMap({ + const ObjectValue value = ObjectValue::FromMap({ {"a", FieldValue::FromString("A")}, {"b", FieldValue::FromMap({ {"ba", FieldValue::FromString("BA")}, {"bb", FieldValue::FromString("BB")}, })}, }); - const FieldValue expected = FieldValue::FromMap({ + const ObjectValue expected = ObjectValue::FromMap({ {"a", FieldValue::FromString("A")}, {"b", FieldValue::FromMap({ {"ba", FieldValue::FromString("BA")}, @@ -539,7 +536,7 @@ TEST(FieldValue, Delete) { } TEST(FieldValue, DeleteNothing) { - const FieldValue value = FieldValue::FromMap({ + const ObjectValue value = ObjectValue::FromMap({ {"a", FieldValue::FromString("A")}, {"b", FieldValue::FromMap({ {"ba", FieldValue::FromString("BA")}, @@ -550,7 +547,7 @@ TEST(FieldValue, DeleteNothing) { } TEST(FieldValue, Get) { - const FieldValue value = FieldValue::FromMap({ + const ObjectValue value = ObjectValue::FromMap({ {"a", FieldValue::FromString("A")}, {"b", FieldValue::FromMap({ {"ba", FieldValue::FromString("BA")}, @@ -563,7 +560,7 @@ TEST(FieldValue, Get) { } TEST(FieldValue, GetNothing) { - const FieldValue value = FieldValue::FromMap({ + const ObjectValue value = ObjectValue::FromMap({ {"a", FieldValue::FromString("A")}, {"b", FieldValue::FromMap({ {"ba", FieldValue::FromString("BA")}, @@ -578,7 +575,7 @@ TEST(FieldValue, IsSmallish) { // We expect the FV to use 4 bytes to track the type of the union, plus 8 // bytes for the union contents themselves. The other 4 is for padding. We // want to keep FV as small as possible. - EXPECT_LE(sizeof(FieldValue), 2 * sizeof(void*)); + EXPECT_LE(sizeof(FieldValue), 2 * sizeof(int64_t)); } } // namespace model diff --git a/Firestore/core/test/firebase/firestore/model/mutation_test.cc b/Firestore/core/test/firebase/firestore/model/mutation_test.cc index 92cce3d29f0..8e3ad04ac1a 100644 --- a/Firestore/core/test/firebase/firestore/model/mutation_test.cc +++ b/Firestore/core/test/firebase/firestore/model/mutation_test.cc @@ -20,6 +20,7 @@ #include "Firestore/core/src/firebase/firestore/model/document.h" #include "Firestore/core/src/firebase/firestore/model/field_value.h" +#include "Firestore/core/src/firebase/firestore/model/maybe_document.h" #include "Firestore/core/test/firebase/firestore/testutil/testutil.h" #include "gtest/gtest.h" @@ -35,10 +36,10 @@ using testutil::PatchMutation; using testutil::SetMutation; TEST(Mutation, AppliesSetsToDocuments) { - auto base_doc = std::make_shared( + MaybeDocumentPtr base_doc = Doc("collection/key", 0, {{"foo", FieldValue::FromString("foo-value")}, - {"baz", FieldValue::FromString("baz-value")}})); + {"baz", FieldValue::FromString("baz-value")}}); std::unique_ptr set = SetMutation( "collection/key", {{"bar", FieldValue::FromString("bar-value")}}); @@ -46,17 +47,17 @@ TEST(Mutation, AppliesSetsToDocuments) { set->ApplyToLocalView(base_doc, base_doc.get(), Timestamp::Now()); ASSERT_NE(set_doc, nullptr); ASSERT_EQ(set_doc->type(), MaybeDocument::Type::Document); - EXPECT_EQ(*set_doc.get(), Doc("collection/key", 0, - {{"bar", FieldValue::FromString("bar-value")}}, - DocumentState::kLocalMutations)); + EXPECT_EQ(*set_doc, *Doc("collection/key", 0, + {{"bar", FieldValue::FromString("bar-value")}}, + DocumentState::kLocalMutations)); } TEST(Mutation, AppliesPatchToDocuments) { - auto base_doc = std::make_shared(Doc( + MaybeDocumentPtr base_doc = Doc( "collection/key", 0, {{"foo", FieldValue::FromMap({{"bar", FieldValue::FromString("bar-value")}})}, - {"baz", FieldValue::FromString("baz-value")}})); + {"baz", FieldValue::FromString("baz-value")}}); std::unique_ptr patch = PatchMutation( "collection/key", {{"foo.bar", FieldValue::FromString("new-bar-value")}}); @@ -64,16 +65,16 @@ TEST(Mutation, AppliesPatchToDocuments) { patch->ApplyToLocalView(base_doc, base_doc.get(), Timestamp::Now()); ASSERT_NE(local, nullptr); EXPECT_EQ( - *local.get(), - Doc("collection/key", 0, - {{"foo", FieldValue::FromMap( - {{"bar", FieldValue::FromString("new-bar-value")}})}, - {"baz", FieldValue::FromString("baz-value")}}, - DocumentState::kLocalMutations)); + *local, + *Doc("collection/key", 0, + {{"foo", FieldValue::FromMap( + {{"bar", FieldValue::FromString("new-bar-value")}})}, + {"baz", FieldValue::FromString("baz-value")}}, + DocumentState::kLocalMutations)); } TEST(Mutation, AppliesPatchWithMergeToDocuments) { - auto base_doc = std::make_shared(DeletedDoc("collection/key", 0)); + MaybeDocumentPtr base_doc = DeletedDoc("collection/key", 0); std::unique_ptr upsert = PatchMutation( "collection/key", {{"foo.bar", FieldValue::FromString("new-bar-value")}}, @@ -82,11 +83,11 @@ TEST(Mutation, AppliesPatchWithMergeToDocuments) { upsert->ApplyToLocalView(base_doc, base_doc.get(), Timestamp::Now()); ASSERT_NE(new_doc, nullptr); EXPECT_EQ( - *new_doc.get(), - Doc("collection/key", 0, - {{"foo", FieldValue::FromMap( - {{"bar", FieldValue::FromString("new-bar-value")}})}}, - DocumentState::kLocalMutations)); + *new_doc, + *Doc("collection/key", 0, + {{"foo", FieldValue::FromMap( + {{"bar", FieldValue::FromString("new-bar-value")}})}}, + DocumentState::kLocalMutations)); } TEST(Mutation, AppliesPatchToNullDocWithMergeToDocuments) { @@ -99,38 +100,38 @@ TEST(Mutation, AppliesPatchToNullDocWithMergeToDocuments) { upsert->ApplyToLocalView(base_doc, base_doc.get(), Timestamp::Now()); ASSERT_NE(new_doc, nullptr); EXPECT_EQ( - *new_doc.get(), - Doc("collection/key", 0, - {{"foo", FieldValue::FromMap( - {{"bar", FieldValue::FromString("new-bar-value")}})}}, - DocumentState::kLocalMutations)); + *new_doc, + *Doc("collection/key", 0, + {{"foo", FieldValue::FromMap( + {{"bar", FieldValue::FromString("new-bar-value")}})}}, + DocumentState::kLocalMutations)); } TEST(Mutation, DeletesValuesFromTheFieldMask) { - auto base_doc = std::make_shared(Doc( + MaybeDocumentPtr base_doc = Doc( "collection/key", 0, {{"foo", FieldValue::FromMap({{"bar", FieldValue::FromString("bar-value")}, - {"baz", FieldValue::FromString("baz-value")}})}})); + {"baz", FieldValue::FromString("baz-value")}})}}); std::unique_ptr patch = - PatchMutation("collection/key", ObjectValue::Empty(), {Field("foo.bar")}); + PatchMutation("collection/key", FieldValue::Map(), {Field("foo.bar")}); MaybeDocumentPtr patch_doc = patch->ApplyToLocalView(base_doc, base_doc.get(), Timestamp::Now()); ASSERT_NE(patch_doc, nullptr); - EXPECT_EQ(*patch_doc.get(), - Doc("collection/key", 0, - {{"foo", FieldValue::FromMap( - {{"baz", FieldValue::FromString("baz-value")}})}}, - DocumentState::kLocalMutations)); + EXPECT_EQ(*patch_doc, + *Doc("collection/key", 0, + {{"foo", FieldValue::FromMap( + {{"baz", FieldValue::FromString("baz-value")}})}}, + DocumentState::kLocalMutations)); } TEST(Mutation, PatchesPrimitiveValue) { - auto base_doc = std::make_shared( + MaybeDocumentPtr base_doc = Doc("collection/key", 0, {{"foo", FieldValue::FromString("foo-value")}, - {"baz", FieldValue::FromString("baz-value")}})); + {"baz", FieldValue::FromString("baz-value")}}); std::unique_ptr patch = PatchMutation( "collection/key", {{"foo.bar", FieldValue::FromString("new-bar-value")}}); @@ -139,22 +140,51 @@ TEST(Mutation, PatchesPrimitiveValue) { patch->ApplyToLocalView(base_doc, base_doc.get(), Timestamp::Now()); ASSERT_NE(patched_doc, nullptr); EXPECT_EQ( - *patched_doc.get(), - Doc("collection/key", 0, - {{"foo", FieldValue::FromMap( - {{"bar", FieldValue::FromString("new-bar-value")}})}, - {"baz", FieldValue::FromString("baz-value")}}, - DocumentState::kLocalMutations)); + *patched_doc, + *Doc("collection/key", 0, + {{"foo", FieldValue::FromMap( + {{"bar", FieldValue::FromString("new-bar-value")}})}, + {"baz", FieldValue::FromString("baz-value")}}, + DocumentState::kLocalMutations)); } TEST(Mutation, PatchingDeletedDocumentsDoesNothing) { - // TODO(rsgowman) + MaybeDocumentPtr base_doc = testutil::DeletedDoc("collection/key", 0); + std::unique_ptr patch = + PatchMutation("collection/key", {{"foo", FieldValue::FromString("bar")}}); + MaybeDocumentPtr patched_doc = + patch->ApplyToLocalView(base_doc, base_doc.get(), Timestamp::Now()); + EXPECT_EQ(base_doc, patched_doc); } TEST(Mutation, AppliesLocalServerTimestampTransformsToDocuments) { // TODO(rsgowman) } +TEST(Mutation, AppliesIncrementTransformToDocument) { + // TODO(rsgowman) +} + +TEST(Mutation, AppliesIncrementTransformToUnexpectedType) { + // TODO(rsgowman) +} + +TEST(Mutation, AppliesIncrementTransformToMissingField) { + // TODO(rsgowman) +} + +TEST(Mutation, AppliesIncrementTransformsConsecutively) { + // TODO(rsgowman) +} + +TEST(Mutation, AppliesIncrementWithoutOverflow) { + // TODO(rsgowman) +} + +TEST(Mutation, AppliesIncrementWithoutUnderflow) { + // TODO(rsgowman) +} + TEST(Mutation, CreatesArrayUnionTransform) { // TODO(rsgowman) } @@ -221,20 +251,20 @@ TEST(Mutation, AppliesServerAckedArrayTransformsToDocuments) { } TEST(Mutation, DeleteDeletes) { - auto base_doc = std::make_shared( - Doc("collection/key", 0, {{"foo", FieldValue::FromString("bar")}})); + MaybeDocumentPtr base_doc = + Doc("collection/key", 0, {{"foo", FieldValue::FromString("bar")}}); std::unique_ptr del = testutil::DeleteMutation("collection/key"); MaybeDocumentPtr deleted_doc = del->ApplyToLocalView(base_doc, base_doc.get(), Timestamp::Now()); ASSERT_NE(deleted_doc, nullptr); - EXPECT_EQ(*deleted_doc.get(), testutil::DeletedDoc("collection/key", 0)); + EXPECT_EQ(*deleted_doc, *testutil::DeletedDoc("collection/key", 0)); } TEST(Mutation, SetWithMutationResult) { - auto base_doc = std::make_shared( - Doc("collection/key", 0, {{"foo", FieldValue::FromString("bar")}})); + MaybeDocumentPtr base_doc = + Doc("collection/key", 0, {{"foo", FieldValue::FromString("bar")}}); std::unique_ptr set = SetMutation( "collection/key", {{"foo", FieldValue::FromString("new-bar")}}); @@ -242,14 +272,14 @@ TEST(Mutation, SetWithMutationResult) { set->ApplyToRemoteDocument(base_doc, MutationResult(4)); ASSERT_NE(set_doc, nullptr); - EXPECT_EQ(*set_doc.get(), Doc("collection/key", 4, - {{"foo", FieldValue::FromString("new-bar")}}, - DocumentState::kCommittedMutations)); + EXPECT_EQ(*set_doc, *Doc("collection/key", 4, + {{"foo", FieldValue::FromString("new-bar")}}, + DocumentState::kCommittedMutations)); } TEST(Mutation, PatchWithMutationResult) { - auto base_doc = std::make_shared( - Doc("collection/key", 0, {{"foo", FieldValue::FromString("bar")}})); + MaybeDocumentPtr base_doc = + Doc("collection/key", 0, {{"foo", FieldValue::FromString("bar")}}); std::unique_ptr patch = PatchMutation( "collection/key", {{"foo", FieldValue::FromString("new-bar")}}); @@ -257,9 +287,9 @@ TEST(Mutation, PatchWithMutationResult) { patch->ApplyToRemoteDocument(base_doc, MutationResult(4)); ASSERT_NE(patch_doc, nullptr); - EXPECT_EQ(*patch_doc.get(), Doc("collection/key", 4, - {{"foo", FieldValue::FromString("new-bar")}}, - DocumentState::kCommittedMutations)); + EXPECT_EQ(*patch_doc, *Doc("collection/key", 4, + {{"foo", FieldValue::FromString("new-bar")}}, + DocumentState::kCommittedMutations)); } TEST(Mutation, Transitions) { diff --git a/Firestore/core/test/firebase/firestore/model/precondition_test.cc b/Firestore/core/test/firebase/firestore/model/precondition_test.cc index a755e29c14f..a610461da34 100644 --- a/Firestore/core/test/firebase/firestore/model/precondition_test.cc +++ b/Firestore/core/test/firebase/firestore/model/precondition_test.cc @@ -28,21 +28,21 @@ namespace firestore { namespace model { TEST(Precondition, None) { - const Precondition none = Precondition::None(); + Precondition none = Precondition::None(); EXPECT_EQ(Precondition::Type::None, none.type()); EXPECT_TRUE(none.IsNone()); EXPECT_EQ(SnapshotVersion::None(), none.update_time()); - const NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); - const Document doc = testutil::Doc("bar/doc", 7654321); + NoDocument deleted_doc = *testutil::DeletedDoc("foo/doc", 1234567); + Document doc = *testutil::Doc("bar/doc", 7654321); EXPECT_TRUE(none.IsValidFor(&deleted_doc)); EXPECT_TRUE(none.IsValidFor(&doc)); EXPECT_TRUE(none.IsValidFor(nullptr)); } TEST(Precondition, Exists) { - const Precondition exists = Precondition::Exists(true); - const Precondition no_exists = Precondition::Exists(false); + Precondition exists = Precondition::Exists(true); + Precondition no_exists = Precondition::Exists(false); EXPECT_EQ(Precondition::Type::Exists, exists.type()); EXPECT_EQ(Precondition::Type::Exists, no_exists.type()); EXPECT_FALSE(exists.IsNone()); @@ -50,8 +50,8 @@ TEST(Precondition, Exists) { EXPECT_EQ(SnapshotVersion::None(), exists.update_time()); EXPECT_EQ(SnapshotVersion::None(), no_exists.update_time()); - const NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); - const Document doc = testutil::Doc("bar/doc", 7654321); + NoDocument deleted_doc = *testutil::DeletedDoc("foo/doc", 1234567); + Document doc = *testutil::Doc("bar/doc", 7654321); EXPECT_FALSE(exists.IsValidFor(&deleted_doc)); EXPECT_TRUE(exists.IsValidFor(&doc)); EXPECT_FALSE(exists.IsValidFor(nullptr)); @@ -61,15 +61,15 @@ TEST(Precondition, Exists) { } TEST(Precondition, UpdateTime) { - const Precondition update_time = + Precondition update_time = Precondition::UpdateTime(testutil::Version(1234567)); EXPECT_EQ(Precondition::Type::UpdateTime, update_time.type()); EXPECT_FALSE(update_time.IsNone()); EXPECT_EQ(testutil::Version(1234567), update_time.update_time()); - const NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); - const Document not_match = testutil::Doc("bar/doc", 7654321); - const Document match = testutil::Doc("baz/doc", 1234567); + NoDocument deleted_doc = *testutil::DeletedDoc("foo/doc", 1234567); + Document not_match = *testutil::Doc("bar/doc", 7654321); + Document match = *testutil::Doc("baz/doc", 1234567); EXPECT_FALSE(update_time.IsValidFor(&deleted_doc)); EXPECT_FALSE(update_time.IsValidFor(¬_match)); EXPECT_TRUE(update_time.IsValidFor(&match)); diff --git a/Firestore/core/test/firebase/firestore/remote/grpc_stream_test.cc b/Firestore/core/test/firebase/firestore/remote/grpc_stream_test.cc index 265ee9c0643..681413a7f1a 100644 --- a/Firestore/core/test/firebase/firestore/remote/grpc_stream_test.cc +++ b/Firestore/core/test/firebase/firestore/remote/grpc_stream_test.cc @@ -196,18 +196,6 @@ TEST_F(GrpcStreamTest, CanGetResponseHeadersAfterFinishing) { }); } -// Death tests should contain the word "DeathTest" in their name -- see -// https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#death-test-naming -using GrpcStreamDeathTest = GrpcStreamTest; - -TEST_F(GrpcStreamDeathTest, CannotRestart) { - worker_queue.EnqueueBlocking([&] { stream->Start(); }); - KeepPollingGrpcQueue(); - worker_queue.EnqueueBlocking([&] { stream->FinishImmediately(); }); - worker_queue.EnqueueBlocking( - [&] { EXPECT_DEATH_IF_SUPPORTED(stream->Start(), ""); }); -} - // Read and write TEST_F(GrpcStreamTest, ReadIsAutomaticallyReadded) { diff --git a/Firestore/core/test/firebase/firestore/remote/grpc_streaming_reader_test.cc b/Firestore/core/test/firebase/firestore/remote/grpc_streaming_reader_test.cc index 0e73cdd4a94..0a1a4c1cb4a 100644 --- a/Firestore/core/test/firebase/firestore/remote/grpc_streaming_reader_test.cc +++ b/Firestore/core/test/firebase/firestore/remote/grpc_streaming_reader_test.cc @@ -136,10 +136,6 @@ TEST_F(GrpcStreamingReaderTest, CanGetResponseHeadersAfterFinishing) { // Method prerequisites -- incorrect usage -// Death tests should contain the word "DeathTest" in their name -- see -// https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#death-test-naming -using GrpcStreamingReaderDeathTest = GrpcStreamingReaderTest; - TEST_F(GrpcStreamingReaderTest, CannotFinishAndNotifyBeforeStarting) { // No callback has been assigned. worker_queue.EnqueueBlocking( diff --git a/Firestore/core/test/firebase/firestore/remote/grpc_unary_call_test.cc b/Firestore/core/test/firebase/firestore/remote/grpc_unary_call_test.cc index 017303ea66e..4d19b235d18 100644 --- a/Firestore/core/test/firebase/firestore/remote/grpc_unary_call_test.cc +++ b/Firestore/core/test/firebase/firestore/remote/grpc_unary_call_test.cc @@ -119,23 +119,6 @@ TEST_F(GrpcUnaryCallTest, CanGetResponseHeadersAfterFinishing) { }); } -// Method prerequisites -- incorrect usage - -// Death tests should contain the word "DeathTest" in their name -- see -// https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#death-test-naming -using GrpcUnaryCallDeathTest = GrpcUnaryCallTest; - -TEST_F(GrpcUnaryCallDeathTest, CannotStartTwice) { - StartCall(); - EXPECT_DEATH_IF_SUPPORTED(StartCall(), ""); -} - -TEST_F(GrpcUnaryCallDeathTest, CannotRestart) { - StartCall(); - ForceFinish({{Type::Finish, CompletionResult::Ok}}); - EXPECT_DEATH_IF_SUPPORTED(StartCall(), ""); -} - TEST_F(GrpcUnaryCallTest, CannotFinishAndNotifyBeforeStarting) { // No callback has been assigned. worker_queue.EnqueueBlocking( diff --git a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc index a7221e68890..ec1c410a636 100644 --- a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc +++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc @@ -36,6 +36,7 @@ #include "Firestore/Protos/cpp/google/firestore/v1/firestore.pb.h" #include "Firestore/core/include/firebase/firestore/firestore_errors.h" #include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/firebase/firestore/model/field_path.h" #include "Firestore/core/src/firebase/firestore/model/field_value.h" #include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "Firestore/core/src/firebase/firestore/nanopb/reader.h" @@ -195,11 +196,10 @@ class SerializerTest : public ::testing::Test { std::vector EncodeDocument(Serializer* serializer, const DocumentKey& key, - const FieldValue& value) { + const ObjectValue& value) { std::vector bytes; Writer writer = Writer::Wrap(&bytes); - google_firestore_v1_Document proto = - serializer->EncodeDocument(key, value.object_value()); + google_firestore_v1_Document proto = serializer->EncodeDocument(key, value); writer.WriteNanopbMessage(google_firestore_v1_Document_fields, &proto); serializer->FreeNanopbMessage(google_firestore_v1_Document_fields, &proto); return bytes; @@ -232,6 +232,16 @@ class SerializerTest : public ::testing::Test { return proto; } + v1::Value ValueProto(double d) { + std::vector bytes = + EncodeFieldValue(&serializer, FieldValue::FromDouble(d)); + v1::Value proto; + bool ok = + proto.ParseFromArray(bytes.data(), static_cast(bytes.size())); + EXPECT_TRUE(ok); + return proto; + } + v1::Value ValueProto(const char* s) { return ValueProto(std::string(s)); } @@ -256,6 +266,36 @@ class SerializerTest : public ::testing::Test { return proto; } + v1::Value ValueProto(const std::vector& blob) { + std::vector bytes = EncodeFieldValue( + &serializer, FieldValue::FromBlob(blob.data(), blob.size())); + v1::Value proto; + bool ok = + proto.ParseFromArray(bytes.data(), static_cast(bytes.size())); + EXPECT_TRUE(ok); + return proto; + } + + v1::Value ValueProto(const GeoPoint& geo_point) { + std::vector bytes = + EncodeFieldValue(&serializer, FieldValue::FromGeoPoint(geo_point)); + v1::Value proto; + bool ok = + proto.ParseFromArray(bytes.data(), static_cast(bytes.size())); + EXPECT_TRUE(ok); + return proto; + } + + v1::Value ValueProto(const std::vector& array) { + std::vector bytes = + EncodeFieldValue(&serializer, FieldValue::FromArray(array)); + v1::Value proto; + bool ok = + proto.ParseFromArray(bytes.data(), static_cast(bytes.size())); + EXPECT_TRUE(ok); + return proto; + } + /** * Creates entries in the proto that we don't care about. * @@ -316,7 +356,7 @@ class SerializerTest : public ::testing::Test { void ExpectSerializationRoundTrip( const DocumentKey& key, - const FieldValue& value, + const ObjectValue& value, const SnapshotVersion& update_time, const v1::BatchGetDocumentsResponse& proto) { std::vector bytes = EncodeDocument(&serializer, key, value); @@ -349,7 +389,7 @@ class SerializerTest : public ::testing::Test { void ExpectDeserializationRoundTrip( const DocumentKey& key, - const absl::optional value, + const absl::optional value, const SnapshotVersion& version, // either update_time or read_time const v1::BatchGetDocumentsResponse& proto) { size_t size = proto.ByteSizeLong(); @@ -424,6 +464,40 @@ TEST_F(SerializerTest, EncodesIntegers) { } } +TEST_F(SerializerTest, EncodesDoubles) { + // Not technically required at all. But if we run into a platform where this + // is false, then we'll have to eliminate a few of our test cases in this + // test. + static_assert(std::numeric_limits::is_iec559, + "IEC559/IEEE764 floating point required"); + + std::vector cases{-std::numeric_limits::infinity(), + std::numeric_limits::lowest(), + std::numeric_limits::min() - 1.0, + -2.0, + -1.1, + -1.0, + -std::numeric_limits::epsilon(), + -std::numeric_limits::min(), + -std::numeric_limits::denorm_min(), + -0.0, + 0.0, + std::numeric_limits::denorm_min(), + std::numeric_limits::min(), + std::numeric_limits::epsilon(), + 1.0, + 1.1, + 2.0, + std::numeric_limits::max() + 1.0, + std::numeric_limits::max(), + std::numeric_limits::infinity()}; + + for (double double_value : cases) { + FieldValue model = FieldValue::FromDouble(double_value); + ExpectRoundTrip(model, ValueProto(double_value), FieldValue::Type::Double); + } +} + TEST_F(SerializerTest, EncodesString) { std::vector cases{ "", @@ -464,6 +538,54 @@ TEST_F(SerializerTest, EncodesTimestamps) { } } +TEST_F(SerializerTest, EncodesBlobs) { + std::vector> cases{ + {}, + {0, 1, 2, 3}, + {0xff, 0x00, 0xff, 0x00}, + }; + + for (const std::vector& blob_value : cases) { + FieldValue model = + FieldValue::FromBlob(blob_value.data(), blob_value.size()); + ExpectRoundTrip(model, ValueProto(blob_value), FieldValue::Type::Blob); + } +} + +TEST_F(SerializerTest, EncodesGeoPoint) { + std::vector cases{ + {1.23, 4.56}, + }; + + for (const GeoPoint& geo_value : cases) { + FieldValue model = FieldValue::FromGeoPoint(geo_value); + ExpectRoundTrip(model, ValueProto(geo_value), FieldValue::Type::GeoPoint); + } +} + +TEST_F(SerializerTest, EncodesArray) { + std::vector> cases{ + // Empty Array. + {}, + // Typical Array. + {FieldValue::FromBoolean(true), FieldValue::FromString("foo")}, + // Nested Array. NB: the protos explicitly state that directly nested + // arrays are not allowed, however arrays *can* contain a map which + // contains another array. + {FieldValue::FromString("foo"), + FieldValue::FromMap( + {{"nested array", + FieldValue::FromArray( + {FieldValue::FromString("nested array value 1"), + FieldValue::FromString("nested array value 2")})}}), + FieldValue::FromString("bar")}}; + + for (const std::vector& array_value : cases) { + FieldValue model = FieldValue::FromArray(array_value); + ExpectRoundTrip(model, ValueProto(array_value), FieldValue::Type::Array); + } +} + TEST_F(SerializerTest, EncodesEmptyMap) { FieldValue model = FieldValue::EmptyObject(); @@ -476,13 +598,13 @@ TEST_F(SerializerTest, EncodesEmptyMap) { TEST_F(SerializerTest, EncodesNestedObjects) { FieldValue model = FieldValue::FromMap({ {"b", FieldValue::True()}, - // TODO(rsgowman): add doubles (once they're supported) - // {"d", FieldValue::DoubleValue(std::numeric_limits::max())}, + {"d", FieldValue::FromDouble(std::numeric_limits::max())}, {"i", FieldValue::FromInteger(1)}, {"n", FieldValue::Null()}, {"s", FieldValue::FromString("foo")}, - // TODO(rsgowman): add arrays (once they're supported) - // {"a", [2, "bar", {"b", false}]}, + {"a", FieldValue::FromArray( + {FieldValue::FromInteger(2), FieldValue::FromString("bar"), + FieldValue::FromMap({{"b", FieldValue::False()}})})}, {"o", FieldValue::FromMap({ {"d", FieldValue::FromInteger(100)}, {"nested", FieldValue::FromMap({ @@ -506,13 +628,24 @@ TEST_F(SerializerTest, EncodesNestedObjects) { (*middle_fields)["d"] = ValueProto(int64_t{100}); (*middle_fields)["nested"] = inner_proto; + v1::Value array_proto; + *array_proto.mutable_array_value()->add_values() = ValueProto(int64_t{2}); + *array_proto.mutable_array_value()->add_values() = ValueProto("bar"); + v1::Value array_inner_proto; + google::protobuf::Map* array_inner_fields = + array_inner_proto.mutable_map_value()->mutable_fields(); + (*array_inner_fields)["b"] = ValueProto(false); + *array_proto.mutable_array_value()->add_values() = array_inner_proto; + v1::Value proto; google::protobuf::Map* fields = proto.mutable_map_value()->mutable_fields(); (*fields)["b"] = ValueProto(true); + (*fields)["d"] = ValueProto(std::numeric_limits::max()); (*fields)["i"] = ValueProto(int64_t{1}); (*fields)["n"] = ValueProto(nullptr); (*fields)["s"] = ValueProto("foo"); + (*fields)["a"] = array_proto; (*fields)["o"] = middle_proto; ExpectRoundTrip(model, proto, FieldValue::Type::Object); @@ -797,7 +930,7 @@ TEST_F(SerializerTest, BadKey) { TEST_F(SerializerTest, EncodesEmptyDocument) { DocumentKey key = DocumentKey::FromPathString("path/to/the/doc"); - FieldValue empty_value = FieldValue::EmptyObject(); + ObjectValue empty_value = ObjectValue::Empty(); SnapshotVersion update_time = SnapshotVersion{{1234, 5678}}; v1::BatchGetDocumentsResponse proto; @@ -817,7 +950,7 @@ TEST_F(SerializerTest, EncodesEmptyDocument) { TEST_F(SerializerTest, EncodesNonEmptyDocument) { DocumentKey key = DocumentKey::FromPathString("path/to/the/doc"); - FieldValue fields = FieldValue::FromMap({ + ObjectValue fields = ObjectValue::FromMap({ {"foo", FieldValue::FromString("bar")}, {"two", FieldValue::FromInteger(2)}, {"nested", FieldValue::FromMap({ diff --git a/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt b/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt index 9d6e4a72515..6d548d943ac 100644 --- a/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt @@ -17,6 +17,7 @@ cc_library( SOURCES app_testing.h app_testing.mm + xcgmock.h DEPENDS FirebaseCore GoogleUtilities diff --git a/Firestore/core/test/firebase/firestore/testutil/testutil.cc b/Firestore/core/test/firebase/firestore/testutil/testutil.cc index e24185408b8..e08678b932b 100644 --- a/Firestore/core/test/firebase/firestore/testutil/testutil.cc +++ b/Firestore/core/test/firebase/firestore/testutil/testutil.cc @@ -24,15 +24,17 @@ namespace testutil { std::unique_ptr PatchMutation( absl::string_view path, - const model::ObjectValue::Map& values, + const model::FieldValue::Map& values, // TODO(rsgowman): Investigate changing update_mask to a set. const std::vector* update_mask) { - model::FieldValue object_value = model::FieldValue::EmptyObject(); + model::ObjectValue object_value = model::ObjectValue::Empty(); std::set object_mask; for (const auto& kv : values) { model::FieldPath field_path = Field(kv.first); object_mask.insert(field_path); + // TODO(rsgowman): This will abort if kv.second.string_value.type() != + // String if (kv.second.string_value() != kDeleteSentinel) { object_value = object_value.Set(field_path, kv.second); } diff --git a/Firestore/core/test/firebase/firestore/testutil/testutil.h b/Firestore/core/test/firebase/firestore/testutil/testutil.h index 0568ef7530b..6d7375b41b1 100644 --- a/Firestore/core/test/firebase/firestore/testutil/testutil.h +++ b/Firestore/core/test/firebase/firestore/testutil/testutil.h @@ -77,23 +77,25 @@ inline model::SnapshotVersion Version(int64_t version) { return model::SnapshotVersion{Timestamp::FromTimePoint(timepoint)}; } -inline model::Document Doc( +inline std::shared_ptr Doc( absl::string_view key, int64_t version = 0, - const model::ObjectValue::Map& data = model::ObjectValue::Empty(), + const model::FieldValue::Map& data = model::FieldValue::Map(), model::DocumentState document_state = model::DocumentState::kSynced) { - return model::Document{model::FieldValue::FromMap(data), Key(key), - Version(version), document_state}; + return std::make_shared(model::ObjectValue::FromMap(data), + Key(key), Version(version), + document_state); } -inline model::NoDocument DeletedDoc(absl::string_view key, int64_t version) { - return model::NoDocument{Key(key), Version(version), - /*has_committed_mutations=*/false}; +inline std::shared_ptr DeletedDoc(absl::string_view key, + int64_t version) { + return std::make_shared(Key(key), Version(version), + /*has_committed_mutations=*/false); } -inline model::UnknownDocument UnknownDoc(absl::string_view key, - int64_t version) { - return model::UnknownDocument{Key(key), Version(version)}; +inline std::shared_ptr UnknownDoc(absl::string_view key, + int64_t version) { + return std::make_shared(Key(key), Version(version)); } inline core::RelationFilter::Operator OperatorFromString(absl::string_view s) { @@ -141,20 +143,20 @@ inline core::Query Query(absl::string_view path) { inline std::unique_ptr SetMutation( absl::string_view path, - const model::ObjectValue::Map& values = model::ObjectValue::Empty()) { + const model::FieldValue::Map& values = model::FieldValue::Map()) { return absl::make_unique( - Key(path), model::FieldValue::FromMap(values), + Key(path), model::ObjectValue::FromMap(values), model::Precondition::None()); } std::unique_ptr PatchMutation( absl::string_view path, - const model::ObjectValue::Map& values = model::ObjectValue::Empty(), + const model::FieldValue::Map& values = model::FieldValue::Map(), const std::vector* update_mask = nullptr); inline std::unique_ptr PatchMutation( absl::string_view path, - const model::ObjectValue::Map& values, + const model::FieldValue::Map& values, const std::vector& update_mask) { return PatchMutation(path, values, &update_mask); } diff --git a/Firestore/core/test/firebase/firestore/testutil/xcgmock.h b/Firestore/core/test/firebase/firestore/testutil/xcgmock.h new file mode 100644 index 00000000000..14ecb206e3f --- /dev/null +++ b/Firestore/core/test/firebase/firestore/testutil/xcgmock.h @@ -0,0 +1,210 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_XCGMOCK_H_ +#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_XCGMOCK_H_ + +#if !defined(__OBJC__) +#error "This header only supports Objective-C++" +#endif // !defined(__OBJC__) + +#import + +#include +#include +#include +#include + +#import "Firestore/Source/Model/FSTDocument.h" + +#include "Firestore/core/src/firebase/firestore/util/string_apple.h" +#include "gmock/gmock.h" + +namespace firebase { +namespace firestore { +namespace testutil { + +template +class XcTestRecorder { + public: + XcTestRecorder(M matcher, XCTestCase* test_case, const char* file, int line) + : formatter_(std::move(matcher)), + test_case_(test_case), + file_(file), + line_(line) { + } + + template + void Match(const char* text, const T& value) const { + testing::AssertionResult result = formatter_(text, value); + if (!result) { + RecordFailure(result.message()); + } + } + + void RecordFailure(const char* message) const { + [test_case_ + recordFailureWithDescription:[NSString stringWithUTF8String:message] + inFile:[NSString stringWithUTF8String:file_] + atLine:line_ + expected:true]; + } + + private: + testing::internal::PredicateFormatterFromMatcher formatter_; + + XCTestCase* test_case_; + const char* file_; + int line_; +}; + +template +XcTestRecorder MakeXcTestRecorder(M matcher, + XCTestCase* test_case, + const char* file, + int line) { + return XcTestRecorder(std::move(matcher), test_case, file, line); +} + +#define XC_ASSERT_THAT(actual, matcher) \ + do { \ + auto recorder = firebase::firestore::testutil::MakeXcTestRecorder( \ + matcher, self, __FILE__, __LINE__); \ + recorder.Match(#actual, actual); \ + } while (0) + +/** + * Prints the -description of an Objective-C object to the given ostream. + */ +inline void ObjcPrintTo(id value, std::ostream* os) { + // Force the result type to NSString* or we can't resolve MakeString. + NSString* description = [value description]; + *os << util::MakeString(description); +} + +} // namespace testutil +} // namespace firestore +} // namespace firebase + +#define OBJC_PRINT_TO(objc_class) \ + @class objc_class; \ + inline void PrintTo(objc_class* value, std::ostream* os) { \ + firebase::firestore::testutil::ObjcPrintTo(value, os); \ + } + +// Define overloads for Objective-C types. Note that each type must be +// explicitly overloaded here because `id` cannot be implicitly converted to +// void* under ARC. If `id` could be converted to void*, then a single overload +// of `operator<<` would be sufficient. + +// Select Foundation types +OBJC_PRINT_TO(NSObject); +OBJC_PRINT_TO(NSArray); +OBJC_PRINT_TO(NSDictionary); +OBJC_PRINT_TO(NSNumber); +OBJC_PRINT_TO(NSString); + +// Declare all Firestore Objective-C classes printable. +// +// Regenerate with: +// find Firestore/Source -name \*.h \ +// | xargs sed -n '/@interface/{ s/<.*//; p; }' \ +// | awk '{ print "OBJC_PRINT_TO(" $2 ");" }' \ +// | sort -u + +OBJC_PRINT_TO(FIRCollectionReference); +OBJC_PRINT_TO(FIRDocumentChange); +OBJC_PRINT_TO(FIRDocumentReference); +OBJC_PRINT_TO(FIRDocumentSnapshot); +OBJC_PRINT_TO(FIRFieldPath); +OBJC_PRINT_TO(FIRFieldValue); +OBJC_PRINT_TO(FIRFirestore); +OBJC_PRINT_TO(FIRFirestoreSettings); +OBJC_PRINT_TO(FIRGeoPoint); +OBJC_PRINT_TO(FIRQuery); +OBJC_PRINT_TO(FIRQueryDocumentSnapshot); +OBJC_PRINT_TO(FIRQuerySnapshot); +OBJC_PRINT_TO(FIRSnapshotMetadata); +OBJC_PRINT_TO(FIRTimestamp); +OBJC_PRINT_TO(FIRTransaction); +OBJC_PRINT_TO(FIRWriteBatch); +OBJC_PRINT_TO(FSTArrayRemoveFieldValue); +OBJC_PRINT_TO(FSTArrayUnionFieldValue); +OBJC_PRINT_TO(FSTArrayValue); +OBJC_PRINT_TO(FSTBlobValue); +OBJC_PRINT_TO(FSTBound); +OBJC_PRINT_TO(FSTDelegateValue); +OBJC_PRINT_TO(FSTDeleteFieldValue); +OBJC_PRINT_TO(FSTDeleteMutation); +OBJC_PRINT_TO(FSTDeletedDocument); +OBJC_PRINT_TO(FSTDocument); +OBJC_PRINT_TO(FSTDocumentKey); +OBJC_PRINT_TO(FSTDocumentKeyReference); +OBJC_PRINT_TO(FSTDocumentSet); +OBJC_PRINT_TO(FSTDoubleValue); +OBJC_PRINT_TO(FSTEventManager); +OBJC_PRINT_TO(FSTFieldValue); +OBJC_PRINT_TO(FSTFieldValueOptions); +OBJC_PRINT_TO(FSTFilter); +OBJC_PRINT_TO(FSTFirestoreClient); +OBJC_PRINT_TO(FSTFirestoreComponent); +OBJC_PRINT_TO(FSTGeoPointValue); +OBJC_PRINT_TO(FSTIntegerValue); +OBJC_PRINT_TO(FSTLRUGarbageCollector); +OBJC_PRINT_TO(FSTLevelDB); +OBJC_PRINT_TO(FSTLevelDBLRUDelegate); +OBJC_PRINT_TO(FSTLimboDocumentChange); +OBJC_PRINT_TO(FSTListenerRegistration); +OBJC_PRINT_TO(FSTLocalDocumentsView); +OBJC_PRINT_TO(FSTLocalSerializer); +OBJC_PRINT_TO(FSTLocalStore); +OBJC_PRINT_TO(FSTLocalViewChanges); +OBJC_PRINT_TO(FSTLocalWriteResult); +OBJC_PRINT_TO(FSTMaybeDocument); +OBJC_PRINT_TO(FSTMemoryEagerReferenceDelegate); +OBJC_PRINT_TO(FSTMemoryLRUReferenceDelegate); +OBJC_PRINT_TO(FSTMemoryPersistence); +OBJC_PRINT_TO(FSTMutation); +OBJC_PRINT_TO(FSTMutationBatch); +OBJC_PRINT_TO(FSTMutationBatchResult); +OBJC_PRINT_TO(FSTMutationResult); +OBJC_PRINT_TO(FSTNanFilter); +OBJC_PRINT_TO(FSTNullFilter); +OBJC_PRINT_TO(FSTNullValue); +OBJC_PRINT_TO(FSTNumberValue); +OBJC_PRINT_TO(FSTNumericIncrementFieldValue); +OBJC_PRINT_TO(FSTObjectValue); +OBJC_PRINT_TO(FSTPatchMutation); +OBJC_PRINT_TO(FSTQuery); +OBJC_PRINT_TO(FSTQueryData); +OBJC_PRINT_TO(FSTReferenceValue); +OBJC_PRINT_TO(FSTRelationFilter); +OBJC_PRINT_TO(FSTSerializerBeta); +OBJC_PRINT_TO(FSTServerTimestampFieldValue); +OBJC_PRINT_TO(FSTServerTimestampValue); +OBJC_PRINT_TO(FSTSetMutation); +OBJC_PRINT_TO(FSTSortOrder); +OBJC_PRINT_TO(FSTStringValue); +OBJC_PRINT_TO(FSTSyncEngine); +OBJC_PRINT_TO(FSTTimestampValue); +OBJC_PRINT_TO(FSTTransformMutation); +OBJC_PRINT_TO(FSTUnknownDocument); +OBJC_PRINT_TO(FSTUserDataConverter); +OBJC_PRINT_TO(FSTView); +OBJC_PRINT_TO(FSTViewChange); +OBJC_PRINT_TO(FSTViewDocumentChanges); + +#endif // FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_XCGMOCK_H_ diff --git a/Firestore/core/test/firebase/firestore/testutil/xcgmock_test.mm b/Firestore/core/test/firebase/firestore/testutil/xcgmock_test.mm new file mode 100644 index 00000000000..5bac8b38d0d --- /dev/null +++ b/Firestore/core/test/firebase/firestore/testutil/xcgmock_test.mm @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/firebase/firestore/testutil/xcgmock.h" + +#import "Firestore/Source/Core/FSTQuery.h" + +#include "Firestore/core/src/firebase/firestore/util/status.h" +#include "Firestore/core/src/firebase/firestore/util/to_string.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace testutil { + +TEST(XcGmockTest, NSArrayPrints) { + std::string expected = util::ToString(@[ @"value" ]); + + EXPECT_EQ(expected, testing::PrintToString(@[ @"value" ])); +} + +TEST(XcGmockTest, NSNumberPrints) { + EXPECT_EQ("1", testing::PrintToString(@1)); +} + +// TODO(wilhuff): make this actually work! +// For whatever reason, this prints like a pointer. +TEST(XcGmockTest, DISABLED_NSStringPrints) { + EXPECT_EQ("value", testing::PrintToString(@"value")); +} + +TEST(XcGmockTest, FSTNullFilterPrints) { + FSTNullFilter* filter = + [[FSTNullFilter alloc] initWithField:model::FieldPath({"field"})]; + EXPECT_EQ("field IS NULL", testing::PrintToString(filter)); +} + +TEST(XcGmockTest, StatusPrints) { + util::Status status(FirestoreErrorCode::NotFound, "missing foo"); + EXPECT_EQ("Not found: missing foo", testing::PrintToString(status)); +} + +TEST(XcGmockTest, TimestampPrints) { + Timestamp timestamp(32, 42); + EXPECT_EQ("Timestamp(seconds=32, nanoseconds=42)", + testing::PrintToString(timestamp)); +} + +} // namespace testutil +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt index 92698a74938..62d46182010 100644 --- a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt @@ -150,9 +150,11 @@ cc_test( autoid_test.cc bits_test.cc comparison_test.cc + delayed_constructor_test.cc hashing_test.cc iterator_adaptors_test.cc ordered_code_test.cc + status_apple_test.mm status_test.cc status_test_util.h statusor_test.cc diff --git a/Firestore/core/test/firebase/firestore/util/delayed_constructor_test.cc b/Firestore/core/test/firebase/firestore/util/delayed_constructor_test.cc new file mode 100644 index 00000000000..56d0ee09442 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/util/delayed_constructor_test.cc @@ -0,0 +1,149 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/util/delayed_constructor.h" + +#include + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace util { + +TEST(DelayedConstructorTest, NoDefaultConstructor) { + static int constructed = 0; + + struct NoDefault { + NoDefault() = delete; + NoDefault(const NoDefault&) = delete; + + explicit NoDefault(int) { + constructed += 1; + } + }; + + DelayedConstructor value; + EXPECT_EQ(0, constructed); + + value.Init(0); + EXPECT_EQ(1, constructed); +} + +TEST(DelayedConstructorTest, NonCopyableType) { + static int constructed = 0; + + struct NonCopyable { + NonCopyable() { + constructed += 1; + } + NonCopyable(const NonCopyable&) = delete; + }; + + DelayedConstructor value; + EXPECT_EQ(0, constructed); + + value.Init(); + EXPECT_EQ(1, constructed); +} + +TEST(DelayedConstructorTest, CopyableType) { + static int constructed = 0; + + struct Copyable { + Copyable() = delete; + Copyable(const Copyable&) { + constructed += 1; + } + + // Backdoor to construct a value without exposing a default constructor + explicit Copyable(int) { + } + }; + + DelayedConstructor value; + EXPECT_EQ(0, constructed); + + value.Init(Copyable(0)); + EXPECT_EQ(1, constructed); +} + +TEST(DelayedConstructorTest, MoveOnlyType) { + static int constructed = 0; + + struct MoveOnly { + MoveOnly() = delete; + MoveOnly(MoveOnly&&) { + constructed += 1; + } + + // Backdoor to construct a value without exposing a default constructor + explicit MoveOnly(int) { + } + }; + + DelayedConstructor value; + EXPECT_EQ(0, constructed); + + value.Init(MoveOnly(0)); + EXPECT_EQ(1, constructed); +} + +TEST(DelayedConstructorTest, CallsDestructor) { + static int constructed = 0; + static int destructed = 0; + + struct Counter { + Counter() { + constructed += 1; + } + + ~Counter() { + destructed += 1; + } + }; + + { + DelayedConstructor value; + EXPECT_EQ(0, constructed); + EXPECT_EQ(0, destructed); + + value.Init(); + EXPECT_EQ(1, constructed); + EXPECT_EQ(0, destructed); + } + + EXPECT_EQ(1, constructed); + EXPECT_EQ(1, destructed); +} + +TEST(DelayedConstructorTest, SingleConstructorArg) { + DelayedConstructor str; + str.Init("foo"); + + EXPECT_EQ(*str, std::string("foo")); +} + +TEST(DelayedConstructorTest, MultipleConstructorArgs) { + DelayedConstructor str; + str.Init(3, 'a'); + + EXPECT_EQ(*str, std::string("aaa")); +} + +} // namespace util +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/util/objc_compatibility_apple_test.mm b/Firestore/core/test/firebase/firestore/util/objc_compatibility_apple_test.mm index 9f89dbf9e7b..8454eaf12ff 100644 --- a/Firestore/core/test/firebase/firestore/util/objc_compatibility_apple_test.mm +++ b/Firestore/core/test/firebase/firestore/util/objc_compatibility_apple_test.mm @@ -19,6 +19,7 @@ #import #include +#include #include #import "Firestore/Example/Tests/Util/FSTHelpers.h" @@ -70,6 +71,60 @@ EXPECT_TRUE([Description(v) isEqual:@"[foo, bar]"]); } +TEST(ObjCCompatibilityTest, EqualToAndHash) { + EqualTo equals; + Hash hash; + + NSMutableString* source = [NSMutableString stringWithUTF8String:"value"]; + NSString* value = [source copy]; + NSString* copy = [source copy]; + + EXPECT_TRUE(equals(value, value)); + EXPECT_EQ(hash(value), hash(value)); + + // Same type, different instance + EXPECT_TRUE(equals(value, copy)); + EXPECT_EQ(hash(value), hash(copy)); + + // Different type, same value + EXPECT_TRUE(equals(source, value)); + EXPECT_EQ(hash(source), hash(value)); + + NSString* other = @"other"; + EXPECT_FALSE(equals(value, other)); + EXPECT_FALSE(equals(value, nil)); + EXPECT_FALSE(equals(nil, value)); + EXPECT_FALSE(equals(nil, nil)); +} + +TEST(ObjCCompatibilityTest, UnorderedMap) { + using MapType = std::unordered_map, + EqualTo>; + MapType map; + + auto inserted = map.insert({ @"foo", @1 }); + ASSERT_TRUE(inserted.second); + + inserted = map.insert({ @"bar", @2 }); + ASSERT_TRUE(inserted.second); + ASSERT_EQ(map.size(), 2); + + auto foo_iter = map.find(@"foo"); + ASSERT_NE(foo_iter, map.end()); + ASSERT_EQ(foo_iter->first, @"foo"); + + auto bar_iter = map.find(@"bar"); + ASSERT_NE(bar_iter, map.end()); + ASSERT_EQ(bar_iter->first, @"bar"); + + auto result = map.insert({ @"foo", @3 }); + ASSERT_FALSE(result.second); // not inserted + ASSERT_TRUE(Equals(result.first->second, @1)); // old value preserved + + map.erase(@"foo"); + ASSERT_EQ(map.size(), 1); +} + } // namespace objc } // namespace util } // namespace firestore diff --git a/Firestore/core/test/firebase/firestore/util/status_apple_test.mm b/Firestore/core/test/firebase/firestore/util/status_apple_test.mm new file mode 100644 index 00000000000..3f466a8c491 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/util/status_apple_test.mm @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/util/status.h" + +#include + +#include "Firestore/core/src/firebase/firestore/util/error_apple.h" +#include "Firestore/core/test/firebase/firestore/util/status_test_util.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace util { + +namespace { + +NSError* MakeNotFound() { + return [NSError + errorWithDomain:NSPOSIXErrorDomain + code:ENOENT + userInfo:@{NSLocalizedDescriptionKey : @"Some file not found"}]; +} + +NSError* MakeCocoaNotFound() { + return [NSError errorWithDomain:NSCocoaErrorDomain + code:NSFileNoSuchFileError + userInfo:@{ + NSLocalizedDescriptionKey : @"Some file not found", + NSUnderlyingErrorKey : MakeNotFound() + }]; +} + +} // namespace + +TEST(Status, MapsPosixErrorCodes) { + Status s = Status::FromNSError(MakeNotFound()); + EXPECT_EQ(FirestoreErrorCode::NotFound, s.code()); + + s = Status::FromNSError(MakeCocoaNotFound()); + EXPECT_EQ(FirestoreErrorCode::NotFound, s.code()); +} + +TEST(Status, PreservesNSError) { + NSError* expected = MakeCocoaNotFound(); + Status s = Status::FromNSError(expected); + + NSError* actual = s.ToNSError(); + EXPECT_TRUE([expected isEqual:actual]); +} + +TEST(Status, CausedBy_Chain_NSError) { + NSError* not_found_nserror = MakeNotFound(); + Status internal_error(FirestoreErrorCode::Internal, "Something broke"); + + Status result = internal_error; + result.CausedBy(Status::FromNSError(not_found_nserror)); + EXPECT_NE(internal_error, result); + + // Outer should prevail + EXPECT_EQ(internal_error.code(), result.code()); + ASSERT_THAT( + result.ToString(), + testing::MatchesRegex( + "Internal: Something broke: Some file not found \\(errno .*\\)")); + + NSError* error = result.ToNSError(); + EXPECT_EQ(internal_error.code(), error.code); + EXPECT_EQ(FIRFirestoreErrorDomain, error.domain); + + NSError* cause = error.userInfo[NSUnderlyingErrorKey]; + EXPECT_TRUE([not_found_nserror isEqual:cause]); +} + +} // namespace util +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/util/status_test.cc b/Firestore/core/test/firebase/firestore/util/status_test.cc index 17cc0d2c85f..9fb980397c6 100644 --- a/Firestore/core/test/firebase/firestore/util/status_test.cc +++ b/Firestore/core/test/firebase/firestore/util/status_test.cc @@ -111,6 +111,9 @@ TEST(Status, FromErrno) { a.ToString(), testing::MatchesRegex( "Already exists: Cannot write file \\(errno .*: File exists\\)")); + + Status b = Status::FromErrno(0, "Nothing wrong"); + ASSERT_EQ(Status::OK(), b); } TEST(Status, CausedBy_OK) { diff --git a/Firestore/core/test/firebase/firestore/util/string_apple_test.mm b/Firestore/core/test/firebase/firestore/util/string_apple_test.mm index 1cc247e1c96..f2a52095de9 100644 --- a/Firestore/core/test/firebase/firestore/util/string_apple_test.mm +++ b/Firestore/core/test/firebase/firestore/util/string_apple_test.mm @@ -64,6 +64,14 @@ } } +TEST_F(StringAppleTest, MakeStringFromNSStringNoCopy) { + for (const std::string& string_value : StringTestCases()) { + NSString* ns_string = WrapNSStringNoCopy(string_value); + std::string actual = MakeString(ns_string); + EXPECT_EQ(string_value, actual); + } +} + } // namespace util } // namespace firestore } // namespace firebase diff --git a/Firestore/fuzzing/leveldb_fuzzer.cc b/Firestore/fuzzing/leveldb_fuzzer.cc index 8533ec69008..6afd83a1c8a 100644 --- a/Firestore/fuzzing/leveldb_fuzzer.cc +++ b/Firestore/fuzzing/leveldb_fuzzer.cc @@ -61,7 +61,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { try { LevelDbMutationKey key; - key.Decode(str); + (void)key.Decode(str); } catch (...) { // Ignore caught errors and assertions. } @@ -75,7 +75,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { try { LevelDbDocumentMutationKey key; - key.Decode(str); + (void)key.Decode(str); } catch (...) { // Ignore caught errors and assertions. } @@ -89,7 +89,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { try { LevelDbMutationQueueKey key; - key.Decode(slice); + (void)key.Decode(str); } catch (...) { // Ignore caught errors and assertions. } @@ -97,7 +97,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { // Test LevelDbTargetGlobalKey methods. try { LevelDbTargetGlobalKey key; - key.Decode(slice); + (void)key.Decode(slice); } catch (...) { // ignore caught errors and assertions. } @@ -105,7 +105,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { // Test LevelDbTargetKey methods. try { LevelDbTargetKey key; - key.Decode(slice); + (void)key.Decode(slice); } catch (...) { // ignore caught errors and assertions. } @@ -119,7 +119,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { try { LevelDbQueryTargetKey key; - key.Decode(str); + (void)key.Decode(str); } catch (...) { // Ignore caught errors and assertions. } @@ -127,7 +127,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { // Test LevelDbTargetDocumentKey methods. try { LevelDbTargetDocumentKey key; - key.Decode(str); + (void)key.Decode(str); } catch (...) { // Ignore caught errors and assertions. } @@ -142,7 +142,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { try { LevelDbDocumentTargetKey key; - key.Decode(str); + (void)key.Decode(str); } catch (...) { // Ignore caught errors and assertions. } @@ -157,7 +157,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { try { LevelDbRemoteDocumentKey key; - key.Decode(str); + (void)key.Decode(str); } catch (...) { // Ignore caught errors and assertions. } diff --git a/Firestore/fuzzing/serializer_fuzzer.cc b/Firestore/fuzzing/serializer_fuzzer.cc index 4657ded5e08..a4d476a394a 100644 --- a/Firestore/fuzzing/serializer_fuzzer.cc +++ b/Firestore/fuzzing/serializer_fuzzer.cc @@ -30,7 +30,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { try { // Try to decode the received data using the serializer. Reader reader = Reader::Wrap(data, size); - auto val = serializer.DecodeFieldValue(&reader); + (void)reader; + // TODO(varconst): reenable this test + // auto val = serializer.DecodeFieldValue(&reader); } catch (...) { // Ignore caught errors and assertions because fuzz testing is looking for // crashes and memory errors. diff --git a/Functions/CHANGELOG.md b/Functions/CHANGELOG.md index 7fa5793ae21..3321f498727 100644 --- a/Functions/CHANGELOG.md +++ b/Functions/CHANGELOG.md @@ -1,3 +1,6 @@ +# v2.4.0 +- Introduce community support for tvOS and macOS (#2506). + # v2.3.0 - Change the default timeout for callable functions to 70s (#2329). - Add a method to change the timeout for a callable (#2329). diff --git a/Functions/FirebaseFunctions/FIRFunctions.m b/Functions/FirebaseFunctions/FIRFunctions.m index 61a594c9eb1..402ee665e16 100644 --- a/Functions/FirebaseFunctions/FIRFunctions.m +++ b/Functions/FirebaseFunctions/FIRFunctions.m @@ -29,10 +29,10 @@ #import "FUNSerializer.h" #import "FUNUsageValidation.h" -#import "FIRApp.h" -#import "FIRAppInternal.h" -#import "FIROptions.h" -#import "GTMSessionFetcherService.h" +#import +#import +#import +#import // The following two macros supply the incantation so that the C // preprocessor does not try to parse the version as a floating diff --git a/Gemfile.lock b/Gemfile.lock index 19a8e920992..5b15e50861c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,7 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.0) - activesupport (4.2.11) + activesupport (4.2.11.1) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) @@ -32,9 +32,9 @@ GEM activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.3) + cocoapods-deintegrate (1.0.4) cocoapods-downloader (1.2.2) - cocoapods-generate (1.3.1) + cocoapods-generate (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) @@ -44,7 +44,7 @@ GEM netrc (~> 0.11) cocoapods-try (1.1.0) colored2 (3.1.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.5) escape (0.0.4) fourflusher (2.2.0) fuzzy_match (2.0.4) @@ -60,7 +60,7 @@ GEM thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - xcodeproj (1.8.1) + xcodeproj (1.8.2) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -75,4 +75,4 @@ DEPENDENCIES cocoapods-generate BUNDLED WITH - 1.16.6 + 1.17.3 diff --git a/GoogleUtilities.podspec b/GoogleUtilities.podspec index b88ba1b7273..8703e2d5c15 100644 --- a/GoogleUtilities.podspec +++ b/GoogleUtilities.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleUtilities' - s.version = '5.3.7' + s.version = '5.8.0' s.summary = 'Google Utilities for iOS (plus community support for macOS and tvOS)' s.description = <<-DESC @@ -19,7 +19,7 @@ other Google CocoaPods. They're not intended for direct public usage. # Technically GoogleUtilites requires iOS 7, but it supports a dependency pod with a minimum # iOS 6, that will do runtime checking to avoid calling into GoogleUtilities. s.ios.deployment_target = '6.0' - s.osx.deployment_target = '10.10' + s.osx.deployment_target = '10.11' s.tvos.deployment_target = '10.0' s.cocoapods_version = '>= 1.4.0' @@ -33,7 +33,7 @@ other Google CocoaPods. They're not intended for direct public usage. s.subspec 'Logger' do |ls| ls.source_files = 'GoogleUtilities/Logger/**/*.[mh]' - ls.public_header_files = 'GoogleUtilities/Logger/Public/*.h' + ls.public_header_files = 'GoogleUtilities/Logger/Private/*.h', 'GoogleUtilities/Logger/Public/*.h' ls.private_header_files = 'GoogleUtilities/Logger/Private/*.h' ls.dependency 'GoogleUtilities/Environment' end diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index bb71991b0e7..cf05a16cf16 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -14,7 +14,7 @@ #import "TargetConditionals.h" -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_TV #import #import @@ -47,14 +47,49 @@ typedef BOOL (*GULRealContinueUserActivityIMP)( id, SEL, UIApplication *, NSUserActivity *, void (^)(NSArray *restorableObjects)); #pragma clang diagnostic pop +typedef void (*GULRealDidRegisterForRemoteNotificationsIMP)(id, SEL, UIApplication *, NSData *); + +typedef void (*GULRealDidFailToRegisterForRemoteNotificationsIMP)(id, + SEL, + UIApplication *, + NSError *); + +typedef void (*GULRealDidReceiveRemoteNotificationIMP)(id, SEL, UIApplication *, NSDictionary *); + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 +// This is needed to for the library to be warning free on iOS versions < 7. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" +typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)( + id, SEL, UIApplication *, NSDictionary *, void (^)(UIBackgroundFetchResult)); +#pragma clang diagnostic pop +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 + typedef void (^GULAppDelegateInterceptorCallback)(id); // The strings below are the keys for associated objects. static char const *const kGULContinueUserActivityIMPKey = "GUL_continueUserActivityIMP"; static char const *const kGULHandleBackgroundSessionIMPKey = "GUL_handleBackgroundSessionIMP"; static char const *const kGULOpenURLOptionsIMPKey = "GUL_openURLOptionsIMP"; + +static char const *const kGULRealDidRegisterForRemoteNotificationsIMPKey = + "GUL_didRegisterForRemoteNotificationsIMP"; +static char const *const kGULRealDidFailToRegisterForRemoteNotificationsIMPKey = + "GUL_didFailToRegisterForRemoteNotificationsIMP"; +static char const *const kGULRealDidReceiveRemoteNotificationIMPKey = + "GUL_didReceiveRemoteNotificationIMP"; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 +static char const *const kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey = + "GUL_didReceiveRemoteNotificationWithCompletionIMP"; +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 + +#if TARGET_OS_IOS +// The method application:openURL:sourceApplication:annotation: is not available on tvOS static char const *const kGULOpenURLOptionsSourceAnnotationsIMPKey = "GUL_openURLSourceApplicationAnnotationIMP"; +#endif // TARGET_OS_IOS + static char const *const kGULRealClassKey = "GUL_realClass"; static NSString *const kGULAppDelegateKeyPath = @"delegate"; @@ -277,9 +312,8 @@ + (void)createSubclassWithObject:(id)anObject { // Create GUL__ NSString *classNameWithPrefix = [kGULAppDelegatePrefix stringByAppendingString:NSStringFromClass(realClass)]; - NSTimeInterval timestamp = [NSDate date].timeIntervalSince1970; NSString *newClassName = - [NSString stringWithFormat:@"%@-%0.0f", classNameWithPrefix, timestamp * 1000]; + [NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString]; if (NSClassFromString(newClassName)) { GULLogError(kGULLoggerSwizzler, NO, @@ -334,19 +368,6 @@ + (void)createSubclassWithObject:(id)anObject { fromClass:realClass]; NSValue *continueUserActivityIMPPointer = [NSValue valueWithPointer:continueUserActivityIMP]; - // For application:openURL:sourceApplication:annotation: - SEL openURLSourceApplicationAnnotationSEL = @selector(application: - openURL:sourceApplication:annotation:); - [GULAppDelegateSwizzler addInstanceMethodWithSelector:openURLSourceApplicationAnnotationSEL - fromClass:[GULAppDelegateSwizzler class] - toClass:appDelegateSubClass]; - GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP = - (GULRealOpenURLSourceApplicationAnnotationIMP)[GULAppDelegateSwizzler - implementationOfMethodSelector:openURLSourceApplicationAnnotationSEL - fromClass:realClass]; - NSValue *openURLSourceAppAnnotationIMPPointer = - [NSValue valueWithPointer:openURLSourceApplicationAnnotationIMP]; - // For application:handleEventsForBackgroundURLSession:completionHandler: SEL handleEventsForBackgroundURLSessionSEL = @selector(application: handleEventsForBackgroundURLSession:completionHandler:); @@ -360,6 +381,59 @@ + (void)createSubclassWithObject:(id)anObject { NSValue *handleBackgroundSessionIMPPointer = [NSValue valueWithPointer:handleBackgroundSessionIMP]; + // For application:didRegisterForRemoteNotificationsWithDeviceToken: + SEL didRegisterForRemoteNotificationsSEL = @selector(application: + didRegisterForRemoteNotificationsWithDeviceToken:); + [GULAppDelegateSwizzler addInstanceMethodWithSelector:didRegisterForRemoteNotificationsSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidRegisterForRemoteNotificationsIMP didRegisterForRemoteNotificationsIMP = + (GULRealDidRegisterForRemoteNotificationsIMP)[GULAppDelegateSwizzler + implementationOfMethodSelector:didRegisterForRemoteNotificationsSEL + fromClass:realClass]; + NSValue *didRegisterForRemoteNotificationsIMPPointer = + [NSValue valueWithPointer:didRegisterForRemoteNotificationsIMP]; + + // For application:didFailToRegisterForRemoteNotificationsWithError: + SEL didFailToRegisterForRemoteNotificationsSEL = @selector(application: + didFailToRegisterForRemoteNotificationsWithError:); + [GULAppDelegateSwizzler addInstanceMethodWithSelector:didFailToRegisterForRemoteNotificationsSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidFailToRegisterForRemoteNotificationsIMP didFailToRegisterForRemoteNotificationsIMP = + (GULRealDidFailToRegisterForRemoteNotificationsIMP)[GULAppDelegateSwizzler + implementationOfMethodSelector:didFailToRegisterForRemoteNotificationsSEL + fromClass:realClass]; + NSValue *didFailToRegisterForRemoteNotificationsIMPPointer = + [NSValue valueWithPointer:didFailToRegisterForRemoteNotificationsIMP]; + + // For application:didReceiveRemoteNotification: + SEL didReceiveRemoteNotificationSEL = @selector(application:didReceiveRemoteNotification:); + [GULAppDelegateSwizzler addInstanceMethodWithSelector:didReceiveRemoteNotificationSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = + (GULRealDidReceiveRemoteNotificationIMP) + [GULAppDelegateSwizzler implementationOfMethodSelector:didReceiveRemoteNotificationSEL + fromClass:realClass]; + NSValue *didReceiveRemoteNotificationIMPPointer = + [NSValue valueWithPointer:didReceiveRemoteNotificationIMP]; + +#if TARGET_OS_IOS + // For application:openURL:sourceApplication:annotation: + SEL openURLSourceApplicationAnnotationSEL = @selector(application: + openURL:sourceApplication:annotation:); + [GULAppDelegateSwizzler addInstanceMethodWithSelector:openURLSourceApplicationAnnotationSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP = + (GULRealOpenURLSourceApplicationAnnotationIMP)[GULAppDelegateSwizzler + implementationOfMethodSelector:openURLSourceApplicationAnnotationSEL + fromClass:realClass]; + NSValue *openURLSourceAppAnnotationIMPPointer = + [NSValue valueWithPointer:openURLSourceApplicationAnnotationIMP]; +#endif // TARGET_OS_IOS + // Override the description too so the custom class name will not show up. [GULAppDelegateSwizzler addInstanceMethodWithDestinationSelector:@selector(description) withImplementationFromSourceSelector:@selector(fakeDescription) @@ -375,11 +449,57 @@ + (void)createSubclassWithObject:(id)anObject { objc_setAssociatedObject(anObject, &kGULOpenURLOptionsIMPKey, openURLOptionsIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + + objc_setAssociatedObject(anObject, &kGULRealDidRegisterForRemoteNotificationsIMPKey, + didRegisterForRemoteNotificationsIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(anObject, &kGULRealDidFailToRegisterForRemoteNotificationsIMPKey, + didFailToRegisterForRemoteNotificationsIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationIMPKey, + didReceiveRemoteNotificationIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + +#if TARGET_OS_IOS objc_setAssociatedObject(anObject, &kGULOpenURLOptionsSourceAnnotationsIMPKey, openURLSourceAppAnnotationIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +#endif // TARGET_OS_IOS + objc_setAssociatedObject(anObject, &kGULRealClassKey, realClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + // For application:didReceiveRemoteNotification:fetchCompletionHandler: +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 + if ([GULAppEnvironmentUtil isIOS7OrHigher]) { + NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer; + SEL didReceiveRemoteNotificationWithCompletionSEL = + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + if ([anObject respondsToSelector:didReceiveRemoteNotificationWithCompletionSEL]) { + // Only add the application:didReceiveRemoteNotification:fetchCompletionHandler: method if + // the original AppDelegate implements it. + // This fixes a bug if an app only implements application:didReceiveRemoteNotification: + // (if we add the method with completion, iOS sees that one exists and does not call + // the method without the completion, which in this case is the only one the app implements). + + [GULAppDelegateSwizzler + addInstanceMethodWithSelector:didReceiveRemoteNotificationWithCompletionSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidReceiveRemoteNotificationWithCompletionIMP + didReceiveRemoteNotificationWithCompletionIMP = + (GULRealDidReceiveRemoteNotificationWithCompletionIMP)[GULAppDelegateSwizzler + implementationOfMethodSelector:didReceiveRemoteNotificationWithCompletionSEL + fromClass:realClass]; + didReceiveRemoteNotificationWithCompletionIMPPointer = + [NSValue valueWithPointer:didReceiveRemoteNotificationWithCompletionIMP]; + } + + objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey, + didReceiveRemoteNotificationWithCompletionIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 + // The subclass size has to be exactly the same size with the original class size. The subclass // cannot have more ivars/properties than its superclass since it will cause an offset in memory // that can lead to overwriting the isa of an object in the next frame. @@ -558,6 +678,8 @@ - (BOOL)application:(UIApplication *)application return returnedValue; } +#if TARGET_OS_IOS + - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication @@ -588,6 +710,8 @@ - (BOOL)application:(UIApplication *)application return returnedValue; } +#endif // TARGET_OS_IOS + #pragma mark - [Donor Methods] Network overridden handler methods #pragma clang diagnostic push @@ -648,7 +772,118 @@ - (BOOL)application:(UIApplication *)application } #pragma clang diagnostic pop +#pragma mark - [Donor Methods] Remote Notifications + +- (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + NSValue *didRegisterForRemoteNotificationsIMPPointer = + objc_getAssociatedObject(self, &kGULRealDidRegisterForRemoteNotificationsIMPKey); + GULRealDidRegisterForRemoteNotificationsIMP didRegisterForRemoteNotificationsIMP = + [didRegisterForRemoteNotificationsIMPPointer pointerValue]; + + // Notify interceptors. + SEL methodSelector = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + [interceptor application:application + didRegisterForRemoteNotificationsWithDeviceToken: + deviceToken]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didRegisterForRemoteNotificationsIMP) { + didRegisterForRemoteNotificationsIMP(self, methodSelector, application, deviceToken); + } +} + +- (void)application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + NSValue *didFailToRegisterForRemoteNotificationsIMPPointer = + objc_getAssociatedObject(self, &kGULRealDidFailToRegisterForRemoteNotificationsIMPKey); + GULRealDidFailToRegisterForRemoteNotificationsIMP didFailToRegisterForRemoteNotificationsIMP = + [didFailToRegisterForRemoteNotificationsIMPPointer pointerValue]; + + // Notify interceptors. + SEL methodSelector = @selector(application:didFailToRegisterForRemoteNotificationsWithError:); + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + [interceptor application:application + didFailToRegisterForRemoteNotificationsWithError:error]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didFailToRegisterForRemoteNotificationsIMP) { + didFailToRegisterForRemoteNotificationsIMP(self, methodSelector, application, error); + } +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 +// This is needed to for the library to be warning free on iOS versions < 7. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer = + objc_getAssociatedObject(self, &kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey); + GULRealDidReceiveRemoteNotificationWithCompletionIMP + didReceiveRemoteNotificationWithCompletionIMP = + [didReceiveRemoteNotificationWithCompletionIMPPointer pointerValue]; + + // Notify interceptors. + SEL methodSelector = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + [interceptor application:application + didReceiveRemoteNotification:userInfo + fetchCompletionHandler:completionHandler]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didReceiveRemoteNotificationWithCompletionIMP) { + didReceiveRemoteNotificationWithCompletionIMP(self, methodSelector, application, userInfo, + completionHandler); + } +} +#pragma clang diagnostic pop +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 + +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo { + NSValue *didReceiveRemoteNotificationIMPPointer = + objc_getAssociatedObject(self, &kGULRealDidReceiveRemoteNotificationIMPKey); + GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = + [didReceiveRemoteNotificationIMPPointer pointerValue]; + + // Notify interceptors. + SEL methodSelector = @selector(application:didReceiveRemoteNotification:); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + [interceptor application:application + didReceiveRemoteNotification:userInfo]; + }]; +#pragma clang diagnostic pop + // Call the real implementation if the real App Delegate has any. + if (didReceiveRemoteNotificationIMP) { + didReceiveRemoteNotificationIMP(self, methodSelector, application, userInfo); + } +} + + (void)proxyAppDelegate:(id)appDelegate { + if (![appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { + GULLogNotice( + kGULLoggerSwizzler, NO, + [NSString + stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate], + @"App Delegate does not conform to UIApplicationDelegate protocol. %@", + [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); + return; + } + id originalDelegate = appDelegate; // Do not create a subclass if it is not enabled. if (![GULAppDelegateSwizzler isAppDelegateProxyEnabled]) { @@ -715,4 +950,4 @@ + (void)resetProxyOriginalDelegateOnceToken { @end -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS || TARGET_OS_TV diff --git a/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h b/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h index 219b220cfd9..6e6b556e7ef 100644 --- a/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h +++ b/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h @@ -23,12 +23,6 @@ NS_ASSUME_NONNULL_BEGIN @interface GULAppDelegateSwizzler () -/** Returns the current sharedApplication. - * - * @return the current UIApplication if in an app, or nil if in extension or if it doesn't exist. - */ -+ (nullable UIApplication *)sharedApplication; - /** ISA Swizzles the given appDelegate as the original app delegate would be. * * @param appDelegate The object that needs to be isa swizzled. This should conform to the diff --git a/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h b/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h index 31fc4b0ab04..4421766458b 100644 --- a/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h +++ b/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h @@ -16,6 +16,7 @@ #import +@class UIApplication; @protocol UIApplicationDelegate; NS_ASSUME_NONNULL_BEGIN @@ -55,6 +56,12 @@ typedef NSString *const GULAppDelegateInterceptorID; */ + (BOOL)isAppDelegateProxyEnabled; +/** Returns the current sharedApplication. + * + * @return the current UIApplication if in an app, or nil if in extension or if it doesn't exist. + */ ++ (nullable UIApplication *)sharedApplication; + /** Do not initialize this class. */ - (instancetype)init NS_UNAVAILABLE; diff --git a/GoogleUtilities/CHANGELOG.md b/GoogleUtilities/CHANGELOG.md index 564ac5a12cc..1335e961b83 100644 --- a/GoogleUtilities/CHANGELOG.md +++ b/GoogleUtilities/CHANGELOG.md @@ -1,5 +1,21 @@ # Unreleased +# 5.7.0 +- Restore to 5.5.0 tag after increased App Store warnings. (#2807) + +# 5.6.0 +- `GULAppDelegateSwizzler`: support of remote notification methods. (#2698) +- `GULAppDelegateSwizzler`: tvOS support. (#2698) + +# 5.5.0 +- Revert 5.4.x changes restoring 5.3.7 version. + +# 5.4.1 +- Fix GULResetLogger API breakage. (#2551) + +# 5.4.0 +- Update GULLogger to use os_log instead of asl_log on iOS 9 and later. (#2374, #2504) + # 5.3.7 - Fixed `pod lib lint GoogleUtilities.podspec --use-libraries` regression. (#2130) - Fixed macOS conditional check in UserDefaults. (#2245) diff --git a/GoogleUtilities/Common/GULLoggerCodes.h b/GoogleUtilities/Common/GULLoggerCodes.h index b71c03797cd..fd22ba637a2 100644 --- a/GoogleUtilities/Common/GULLoggerCodes.h +++ b/GoogleUtilities/Common/GULLoggerCodes.h @@ -16,20 +16,21 @@ typedef NS_ENUM(NSInteger, GULSwizzlerMessageCode) { // App Delegate Swizzling. - kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000 - kGULSwizzlerMessageCodeAppDelegateSwizzling001 = 1001, // I-SWZ001001 - kGULSwizzlerMessageCodeAppDelegateSwizzling002 = 1002, // I-SWZ001002 - kGULSwizzlerMessageCodeAppDelegateSwizzling003 = 1003, // I-SWZ001003 - kGULSwizzlerMessageCodeAppDelegateSwizzling004 = 1004, // I-SWZ001004 - kGULSwizzlerMessageCodeAppDelegateSwizzling005 = 1005, // I-SWZ001005 - kGULSwizzlerMessageCodeAppDelegateSwizzling006 = 1006, // I-SWZ001006 - kGULSwizzlerMessageCodeAppDelegateSwizzling007 = 1007, // I-SWZ001007 - kGULSwizzlerMessageCodeAppDelegateSwizzling008 = 1008, // I-SWZ001008 - kGULSwizzlerMessageCodeAppDelegateSwizzling009 = 1009, // I-SWZ001009 - kGULSwizzlerMessageCodeAppDelegateSwizzling010 = 1010, // I-SWZ001010 - kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011 - kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012 - kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013 + kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000 + kGULSwizzlerMessageCodeAppDelegateSwizzling001 = 1001, // I-SWZ001001 + kGULSwizzlerMessageCodeAppDelegateSwizzling002 = 1002, // I-SWZ001002 + kGULSwizzlerMessageCodeAppDelegateSwizzling003 = 1003, // I-SWZ001003 + kGULSwizzlerMessageCodeAppDelegateSwizzling004 = 1004, // I-SWZ001004 + kGULSwizzlerMessageCodeAppDelegateSwizzling005 = 1005, // I-SWZ001005 + kGULSwizzlerMessageCodeAppDelegateSwizzling006 = 1006, // I-SWZ001006 + kGULSwizzlerMessageCodeAppDelegateSwizzling007 = 1007, // I-SWZ001007 + kGULSwizzlerMessageCodeAppDelegateSwizzling008 = 1008, // I-SWZ001008 + kGULSwizzlerMessageCodeAppDelegateSwizzling009 = 1009, // I-SWZ001009 + kGULSwizzlerMessageCodeAppDelegateSwizzling010 = 1010, // I-SWZ001010 + kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011 + kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012 + kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013 + kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate = 1014, // I-SWZ001014 // Method Swizzling. kGULSwizzlerMessageCodeMethodSwizzling000 = 2000, // I-SWZ002000 diff --git a/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.h b/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.h index 5b562719cb4..d5502647c20 100644 --- a/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.h +++ b/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.h @@ -40,4 +40,7 @@ /// Indicates whether it is running inside an extension or an app. + (BOOL)isAppExtension; +/// @return Returns @YES when is run on iOS version greater or equal to 7.0 ++ (BOOL)isIOS7OrHigher; + @end diff --git a/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m b/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m index 1fa767b1856..8327438ad8d 100644 --- a/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m +++ b/GoogleUtilities/Environment/third_party/GULAppEnvironmentUtil.m @@ -247,4 +247,16 @@ + (BOOL)isAppExtension { #endif } ++ (BOOL)isIOS7OrHigher { +#if __has_builtin(__builtin_available) + if (@available(iOS 7.0, *)) { +#else + if ([[UIDevice currentDevice].systemVersion integerValue] >= 7) { +#endif + return YES; + } + + return NO; +} + @end diff --git a/GoogleUtilities/Environment/third_party/LICENSE b/GoogleUtilities/Environment/third_party/LICENSE new file mode 100644 index 00000000000..78dd6c9770b --- /dev/null +++ b/GoogleUtilities/Environment/third_party/LICENSE @@ -0,0 +1,36 @@ +The following copyright from Landon J. Fuller applies to the isAppEncrypted function. + +Copyright (c) 2017 Landon J. Fuller +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Comment from iPhone Dev Wiki +Crack Prevention: +App Store binaries are signed by both their developer and Apple. This encrypts the binary so +that decryption keys are needed in order to make the binary readable. When iOS executes the +binary, the decryption keys are used to decrypt the binary into a readable state where it is +then loaded into memory and executed. iOS can tell the encryption status of a binary via the +cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero +value then the binary is encrypted. + +'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into +a new binary file, resigning, and repackaging. This will only work on jailbroken devices as +codesignature validation has been removed. Resigning takes place because while the codesignature +doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have +AppSync or similar to disable codesignature checks. + +More information at Landon Fuller's blog diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj index 880663d42fc..30940fb98ea 100644 --- a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj +++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj @@ -13,13 +13,9 @@ 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; - C8042CCE220C7B69009A8CCF /* GULASLLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C8042CCD220C7B69009A8CCF /* GULASLLoggerTest.m */; }; - C8BE7EFC22110DA200AC306B /* GULOSLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C8BE7EFB22110DA200AC306B /* GULOSLoggerTest.m */; }; - C8BE7EFD22110DA200AC306B /* GULOSLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C8BE7EFB22110DA200AC306B /* GULOSLoggerTest.m */; }; - C8BE7EFE22110DA200AC306B /* GULOSLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C8BE7EFB22110DA200AC306B /* GULOSLoggerTest.m */; }; + 9A414672225259F900B08D77 /* GULAppDelegateSwizzlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = EFBE67F92101401100E756A7 /* GULAppDelegateSwizzlerTest.m */; }; + 9A7C37C2224BD9C600033B0D /* GULAppDelegateSwizzlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = EFBE67F92101401100E756A7 /* GULAppDelegateSwizzlerTest.m */; }; DE5CF98E20F686310063FFDD /* GULAppEnvironmentUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */; }; - DE83CAE122230DEC002003C1 /* GULASLLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C8042CCD220C7B69009A8CCF /* GULASLLoggerTest.m */; }; - DE83CAE222230DED002003C1 /* GULASLLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C8042CCD220C7B69009A8CCF /* GULASLLoggerTest.m */; }; DE84BBC421D7EC900048A176 /* GULUserDefaultsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE84BBC321D7EC900048A176 /* GULUserDefaultsTests.m */; }; DE84BBC521D7EC900048A176 /* GULUserDefaultsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE84BBC321D7EC900048A176 /* GULUserDefaultsTests.m */; }; DE84BBC621D7EC900048A176 /* GULUserDefaultsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE84BBC321D7EC900048A176 /* GULUserDefaultsTests.m */; }; @@ -98,9 +94,7 @@ 6003F5AE195388D20070C39A /* Tests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; - 7BEA793625C8DE7C8EC60006 /* GoogleUtilities.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = GoogleUtilities.podspec; path = ../GoogleUtilities.podspec; sourceTree = ""; }; - C8042CCD220C7B69009A8CCF /* GULASLLoggerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GULASLLoggerTest.m; sourceTree = ""; }; - C8BE7EFB22110DA200AC306B /* GULOSLoggerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULOSLoggerTest.m; sourceTree = ""; }; + 7BEA793625C8DE7C8EC60006 /* GoogleUtilities.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = GoogleUtilities.podspec; path = ../../GoogleUtilities.podspec; sourceTree = ""; }; DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULAppEnvironmentUtilTest.m; sourceTree = ""; }; DE84BBC321D7EC900048A176 /* GULUserDefaultsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULUserDefaultsTests.m; sourceTree = ""; }; DEC977D320F68C3300014E20 /* GULReachabilityCheckerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULReachabilityCheckerTest.m; sourceTree = ""; }; @@ -319,8 +313,6 @@ isa = PBXGroup; children = ( DEC977DF20F6A7A700014E20 /* GULLoggerTest.m */, - C8042CCD220C7B69009A8CCF /* GULASLLoggerTest.m */, - C8BE7EFB22110DA200AC306B /* GULOSLoggerTest.m */, ); path = Logger; sourceTree = ""; @@ -626,14 +618,13 @@ DEC977D820F68C3300014E20 /* GULMutableDictionaryTest.m in Sources */, DEC977E120F6A7C100014E20 /* GULLoggerTest.m in Sources */, DEC977D920F68C3300014E20 /* GULNetworkTest.m in Sources */, - C8042CCE220C7B69009A8CCF /* GULASLLoggerTest.m in Sources */, EFBE67FA2101401100E756A7 /* GULSwizzlerTest.m in Sources */, EFBE67FD2101401100E756A7 /* GULObjectSwizzlerTest.m in Sources */, + 9A7C37C2224BD9C600033B0D /* GULAppDelegateSwizzlerTest.m in Sources */, EFBE67FE2101401100E756A7 /* GULRuntimeClassSnapshotTests.m in Sources */, EFBE67FC2101401100E756A7 /* GULRuntimeClassDiffTests.m in Sources */, DE5CF98E20F686310063FFDD /* GULAppEnvironmentUtilTest.m in Sources */, DEC977D720F68C3300014E20 /* GULReachabilityCheckerTest.m in Sources */, - C8BE7EFC22110DA200AC306B /* GULOSLoggerTest.m in Sources */, EFBE67FB2101401100E756A7 /* GULSwizzlingCacheTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -658,8 +649,6 @@ DE84BBC521D7EC900048A176 /* GULUserDefaultsTests.m in Sources */, DEC9781D20F6D39900014E20 /* GTMHTTPServer.m in Sources */, DEC9781B20F6D39500014E20 /* GULMutableDictionaryTest.m in Sources */, - DE83CAE122230DEC002003C1 /* GULASLLoggerTest.m in Sources */, - C8BE7EFD22110DA200AC306B /* GULOSLoggerTest.m in Sources */, DEC9781C20F6D39500014E20 /* GULNetworkTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -683,9 +672,8 @@ DEC9786A20F6D66300014E20 /* GULMutableDictionaryTest.m in Sources */, DE84BBC621D7EC900048A176 /* GULUserDefaultsTests.m in Sources */, DEC9786C20F6D66700014E20 /* GULReachabilityCheckerTest.m in Sources */, + 9A414672225259F900B08D77 /* GULAppDelegateSwizzlerTest.m in Sources */, DEC9786820F6D65B00014E20 /* GULLoggerTest.m in Sources */, - DE83CAE222230DED002003C1 /* GULASLLoggerTest.m in Sources */, - C8BE7EFE22110DA200AC306B /* GULOSLoggerTest.m in Sources */, DEC9786D20F6D66B00014E20 /* GULAppEnvironmentUtilTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -902,7 +890,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../Reachability", - "$(SRCROOT)/../Logger", + "$(SRCROOT)/../AppDelegateSwizzler/Internal", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; @@ -926,7 +914,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../Reachability", - "$(SRCROOT)/../Logger", + "$(SRCROOT)/../AppDelegateSwizzler/Internal", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; @@ -1005,7 +993,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../Reachability", - "$(SRCROOT)/../Logger", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; @@ -1037,7 +1024,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../Reachability", - "$(SRCROOT)/../Logger", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; @@ -1117,7 +1103,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../Reachability", - "$(SRCROOT)/../Logger", + "$(SRCROOT)/../AppDelegateSwizzler/Internal", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1148,7 +1134,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../Reachability", - "$(SRCROOT)/../Logger", + "$(SRCROOT)/../AppDelegateSwizzler/Internal", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme index f4ef27750b9..31b6b579ff4 100644 --- a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme +++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + codeCoverageEnabled = "YES" shouldUseLaunchSchemeArgsEnv = "YES"> '../../' @@ -42,3 +32,13 @@ target 'Example_tvOS' do pod 'OCMock' end end + +post_install do |installer_representation| + installer_representation.pods_project.targets.each do |target| + target.build_configurations.each do |config| + if config.name != 'Release' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'GUL_UNSWIZZLING_ENABLED=1', 'GUL_APP_DELEGATE_TESTING=1'] + end + end + end +end \ No newline at end of file diff --git a/GoogleUtilities/Example/Tests/Logger/GULASLLoggerTest.m b/GoogleUtilities/Example/Tests/Logger/GULASLLoggerTest.m deleted file mode 100644 index 642db000854..00000000000 --- a/GoogleUtilities/Example/Tests/Logger/GULASLLoggerTest.m +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2019 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import -#import "GULASLLogger.h" - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -// TODO(bstpierre): Use a C function redirect to mock asl_* methods like GULOSLoggerTest. - -static NSString *const kService = @"my service"; -static NSString *const kCode = @"I-COR000001"; - -// Redefine class property as readwrite for testing. -@interface GULLogger (ForTesting) -@property(nonatomic, nullable, class, readwrite) id logger; -@end - -// Surface aslclient and dispatchQueues for tests. -@interface GULASLLogger (ForTesting) -@property(nonatomic) aslclient aslClient; -@property(nonatomic) dispatch_queue_t dispatchQueue; -@end - -#pragma mark - - -@interface GULASLLoggerTest : XCTestCase -@property(nonatomic, nullable) GULASLLogger *logger; -@end - -@implementation GULASLLoggerTest - -#pragma mark Helper Methods - -// TODO(bstpierre): Replace this with a XCTestExpectation like GULOSLoggerTest. -- (BOOL)messageWasLogged:(NSString *)message { - // Format the message as it's expected. - message = [NSString stringWithFormat:@"%@[%@] %@", kService, kCode, message]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - aslmsg query = asl_new(ASL_TYPE_QUERY); - asl_set_query(query, ASL_KEY_FACILITY, "com.google.utilities.logger", ASL_QUERY_OP_EQUAL); - aslresponse response = asl_search(self.logger.aslClient, query); - asl_release(query); - aslmsg msg; - const char *responseMsg; - BOOL messageFound = NO; - while ((msg = asl_next(response)) != NULL) { - responseMsg = asl_get(msg, ASL_KEY_MSG); - if ([message isEqualToString:[NSString stringWithUTF8String:responseMsg]]) { - messageFound = YES; - break; - } - } - asl_release(msg); - asl_release(response); -#pragma clang pop - return messageFound; -} - -#pragma mark Testing - -- (void)setUp { - [super setUp]; - self.logger = [[GULASLLogger alloc] init]; - GULLogger.logger = self.logger; -} - -- (void)tearDown { - GULLogger.logger = nil; - self.logger = nil; - [super tearDown]; -} - -- (void)testMessageCodeFormat { - // Valid case. - XCTAssertNoThrow(GULLogError(kService, NO, @"I-APP000001", @"Message.")); - - // An extra dash or missing dash should fail. - XCTAssertThrows(GULLogError(kService, NO, @"I-APP-000001", @"Message.")); - XCTAssertThrows(GULLogError(kService, NO, @"IAPP000001", @"Message.")); - - // Wrong number of digits should fail. - XCTAssertThrows(GULLogError(kService, NO, @"I-APP00001", @"Message.")); - XCTAssertThrows(GULLogError(kService, NO, @"I-APP0000001", @"Message.")); - - // Lowercase should fail. - XCTAssertThrows(GULLogError(kService, NO, @"I-app000001", @"Message.")); - - // nil or empty message code should fail. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnonnull" - XCTAssertThrows(GULLogError(kService, NO, nil, @"Message.")); -#pragma clang diagnostic pop - - XCTAssertThrows(GULLogError(kService, NO, @"", @"Message.")); - - // Android message code should fail. - XCTAssertThrows(GULLogError(kService, NO, @"A-APP000001", @"Message.")); -} - -- (void)testLoggerInterface { - XCTAssertNoThrow(GULLogError(kService, NO, kCode, @"Message.")); - XCTAssertNoThrow(GULLogError(kService, NO, kCode, @"Configure %@.", @"blah")); - - XCTAssertNoThrow(GULLogWarning(kService, NO, kCode, @"Message.")); - XCTAssertNoThrow(GULLogWarning(kService, NO, kCode, @"Configure %@.", @"blah")); - - XCTAssertNoThrow(GULLogNotice(kService, NO, kCode, @"Message.")); - XCTAssertNoThrow(GULLogNotice(kService, NO, kCode, @"Configure %@.", @"blah")); - - XCTAssertNoThrow(GULLogInfo(kService, NO, kCode, @"Message.")); - XCTAssertNoThrow(GULLogInfo(kService, NO, kCode, @"Configure %@.", @"blah")); - - XCTAssertNoThrow(GULLogDebug(kService, NO, kCode, @"Message.")); - XCTAssertNoThrow(GULLogDebug(kService, NO, kCode, @"Configure %@.", @"blah")); -} - -// The GULLoggerLevel enum must match the ASL_LEVEL_* constants, but we manually redefine -// them in GULLoggerLevel.h since we cannot include (see b/34976089 for more details). -// This test ensures the constants match. -- (void)testGULLoggerLevelValues { - XCTAssertEqual(GULLoggerLevelError, ASL_LEVEL_ERR); - XCTAssertEqual(GULLoggerLevelWarning, ASL_LEVEL_WARNING); - XCTAssertEqual(GULLoggerLevelNotice, ASL_LEVEL_NOTICE); - XCTAssertEqual(GULLoggerLevelInfo, ASL_LEVEL_INFO); - XCTAssertEqual(GULLoggerLevelDebug, ASL_LEVEL_DEBUG); -} - -// TODO(bstpierre): Add tests for logWithLevel:withService:isForced:withCode:withMessage: - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Example/Tests/Logger/GULLoggerTest.m b/GoogleUtilities/Example/Tests/Logger/GULLoggerTest.m index 01b7fad5b70..9483bbcb5f9 100644 --- a/GoogleUtilities/Example/Tests/Logger/GULLoggerTest.m +++ b/GoogleUtilities/Example/Tests/Logger/GULLoggerTest.m @@ -12,92 +12,184 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifdef DEBUG +// The tests depend upon library methods only built with #ifdef DEBUG + #import #import #import -static GULLoggerLevel kLogLevel = GULLoggerLevelError; -static NSString *const kService = @"Test Service"; -static NSString *const kCode = @"I-COR000001"; -static NSString *const kLogMessage = @"Log Message"; -static NSString *const kVersionString = @"2"; -static char *const kVersionChar = "2"; +#import -// Redefine class property as readwrite for testing. -@interface GULLogger (ForTesting) -@property(nonatomic, class, readwrite) id logger; -@end +extern const char *kGULLoggerASLClientFacilityName; + +extern void GULResetLogger(void); + +extern aslclient getGULLoggerClient(void); + +extern dispatch_queue_t getGULClientQueue(void); -#pragma mark - +extern BOOL getGULLoggerDebugMode(void); + +static NSString *const kMessageCode = @"I-COR000001"; @interface GULLoggerTest : XCTestCase -@property(nonatomic) id loggerSystemMock; + +@property(nonatomic) NSString *randomLogString; + +@property(nonatomic, strong) NSUserDefaults *defaults; + @end @implementation GULLoggerTest - (void)setUp { [super setUp]; - self.loggerSystemMock = OCMProtocolMock(@protocol(GULLoggerSystem)); - GULLogger.logger = self.loggerSystemMock; + GULResetLogger(); + + // Stub NSUserDefaults for cleaner testing. + _defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.google.logger_test"]; } - (void)tearDown { - GULLogger.logger = nil; - [self.loggerSystemMock stopMocking]; - self.loggerSystemMock = nil; [super tearDown]; + + _defaults = nil; } -#pragma mark Initialization Tests +- (void)testMessageCodeFormat { + // Valid case. + XCTAssertNoThrow(GULLogError(@"my service", NO, @"I-APP000001", @"Message.")); -- (void)testInitializeEmpty { - [[self.loggerSystemMock expect] initializeLogger]; - GULLoggerInitialize(); - [self.loggerSystemMock verify]; -} + // An extra dash or missing dash should fail. + XCTAssertThrows(GULLogError(@"my service", NO, @"I-APP-000001", @"Message.")); + XCTAssertThrows(GULLogError(@"my service", NO, @"IAPP000001", @"Message.")); + + // Wrong number of digits should fail. + XCTAssertThrows(GULLogError(@"my service", NO, @"I-APP00001", @"Message.")); + XCTAssertThrows(GULLogError(@"my service", NO, @"I-APP0000001", @"Message.")); + + // Lowercase should fail. + XCTAssertThrows(GULLogError(@"my service", NO, @"I-app000001", @"Message.")); + +// nil or empty message code should fail. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + XCTAssertThrows(GULLogError(@"my service", NO, nil, @"Message.")); +#pragma clang diagnostic pop -- (void)testInitializeTwice { - [[self.loggerSystemMock expect] initializeLogger]; - GULLoggerInitialize(); - GULLoggerInitialize(); - [self.loggerSystemMock verify]; + XCTAssertThrows(GULLogError(@"my service", NO, @"", @"Message.")); + + // Android message code should fail. + XCTAssertThrows(GULLogError(@"my service", NO, @"A-APP000001", @"Message.")); } -#pragma mark Forwarded Call Tests +- (void)testLoggerInterface { + XCTAssertNoThrow(GULLogError(@"my service", NO, kMessageCode, @"Message.")); + XCTAssertNoThrow(GULLogError(@"my service", NO, kMessageCode, @"Configure %@.", @"blah")); + + XCTAssertNoThrow(GULLogWarning(@"my service", NO, kMessageCode, @"Message.")); + XCTAssertNoThrow(GULLogWarning(@"my service", NO, kMessageCode, @"Configure %@.", @"blah")); + + XCTAssertNoThrow(GULLogNotice(@"my service", NO, kMessageCode, @"Message.")); + XCTAssertNoThrow(GULLogNotice(@"my service", NO, kMessageCode, @"Configure %@.", @"blah")); -- (void)testForceDebug { - [[self.loggerSystemMock expect] setForcedDebug:YES]; - GULLoggerForceDebug(); - [self.loggerSystemMock verify]; + XCTAssertNoThrow(GULLogInfo(@"my service", NO, kMessageCode, @"Message.")); + XCTAssertNoThrow(GULLogInfo(@"my service", NO, kMessageCode, @"Configure %@.", @"blah")); + + XCTAssertNoThrow(GULLogDebug(@"my service", NO, kMessageCode, @"Message.")); + XCTAssertNoThrow(GULLogDebug(@"my service", NO, kMessageCode, @"Configure %@.", @"blah")); } -- (void)testEnableSTDERR { - [[self.loggerSystemMock expect] printToSTDERR]; - GULLoggerEnableSTDERR(); - [self.loggerSystemMock verify]; +// asl_set_filter does not perform as expected in unit test environment with simulator. The +// following test only checks whether the logs have been sent to system with the default settings in +// the unit test environment. +- (void)testSystemLogWithDefaultStatus { +#if !(BUG128) // Disable until https://github.com/firebase/firebase-ios-sdk/issues/128 is fixed + // Test fails on device and iOS 9 simulators - b/38130372 + return; +#else + // Sets the time interval that we need to wait in order to fetch all the logs. + NSTimeInterval timeInterval = 0.1f; + // Generates a random string each time and check whether it has been logged. + // Log messages with Notice level and below should be logged to system/device by default. + self.randomLogString = [NSUUID UUID].UUIDString; + GULLogError(@"my service", NO, kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertTrue([self logExists]); + + self.randomLogString = [NSUUID UUID].UUIDString; + GULLogWarning(@"my service", kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertTrue([self logExists]); + + self.randomLogString = [NSUUID UUID].UUIDString; + GULLogNotice(@"my service", kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertTrue([self logExists]); + + // Log messages with Info level and above should NOT be logged to system/device by default. + self.randomLogString = [NSUUID UUID].UUIDString; + GULLogInfo(@"my service", kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertFalse([self logExists]); + + self.randomLogString = [NSUUID UUID].UUIDString; + GULLogDebug(@"my service", kMessageCode, @"%@", self.randomLogString); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]]; + XCTAssertFalse([self logExists]); +#endif } -- (void)testSetLoggerLevel { - [[self.loggerSystemMock expect] setLogLevel:kLogLevel]; - GULSetLoggerLevel(kLogLevel); - [self.loggerSystemMock verify]; +// The GULLoggerLevel enum must match the ASL_LEVEL_* constants, but we manually redefine +// them in GULLoggerLevel.h since we cannot include (see b/34976089 for more details). +// This test ensures the constants match. +- (void)testGULLoggerLevelValues { + XCTAssertEqual(GULLoggerLevelError, ASL_LEVEL_ERR); + XCTAssertEqual(GULLoggerLevelWarning, ASL_LEVEL_WARNING); + XCTAssertEqual(GULLoggerLevelNotice, ASL_LEVEL_NOTICE); + XCTAssertEqual(GULLoggerLevelInfo, ASL_LEVEL_INFO); + XCTAssertEqual(GULLoggerLevelDebug, ASL_LEVEL_DEBUG); } -- (void)testIsLoggableLevel { - [[self.loggerSystemMock expect] isLoggableLevel:kLogLevel]; - GULIsLoggableLevel(kLogLevel); - [self.loggerSystemMock verify]; +// Helper functions. +- (BOOL)logExists { + [self drainGULClientQueue]; + NSString *correctMsg = + [NSString stringWithFormat:@"%@[%@] %@", @"my service", kMessageCode, self.randomLogString]; + return [self messageWasLogged:correctMsg]; } -- (void)testRegisterVersion { - [[self.loggerSystemMock expect] setVersion:kVersionString]; - GULLoggerRegisterVersion(kVersionChar); - [self.loggerSystemMock verify]; +- (void)drainGULClientQueue { + dispatch_semaphore_t workerSemaphore = dispatch_semaphore_create(0); + dispatch_async(getGULClientQueue(), ^{ + dispatch_semaphore_signal(workerSemaphore); + }); + dispatch_semaphore_wait(workerSemaphore, DISPATCH_TIME_FOREVER); } -// TODO(bstpierre): Test that LogBasic calls are piped through. OCMock does not currently support -// the mocking of methods with variadic parameters: https://github.com/erikdoe/ocmock/issues/191 +- (BOOL)messageWasLogged:(NSString *)message { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + aslmsg query = asl_new(ASL_TYPE_QUERY); + asl_set_query(query, ASL_KEY_FACILITY, kGULLoggerASLClientFacilityName, ASL_QUERY_OP_EQUAL); + aslresponse r = asl_search(getGULLoggerClient(), query); + asl_free(query); + aslmsg m; + const char *val; + NSMutableArray *allMsg = [[NSMutableArray alloc] init]; + while ((m = asl_next(r)) != NULL) { + val = asl_get(m, ASL_KEY_MSG); + if (val) { + [allMsg addObject:[NSString stringWithUTF8String:val]]; + } + } + asl_free(m); + asl_release(r); + return [allMsg containsObject:message]; +#pragma clang pop +} @end +#endif diff --git a/GoogleUtilities/Example/Tests/Logger/GULOSLoggerTest.m b/GoogleUtilities/Example/Tests/Logger/GULOSLoggerTest.m deleted file mode 100644 index f9ad7bc2879..00000000000 --- a/GoogleUtilities/Example/Tests/Logger/GULOSLoggerTest.m +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2019 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "GULOSLogger.h" - -#import -#import -#import - -#import - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const kService = @"my service"; -static NSString *const kCode = @"I-COR000001"; -static NSTimeInterval const kTimeout = 1.0; - -// Expectation that contains the information needed to see if the correct parameters were used in an -// os_log_with_type call. -@interface GULOSLoggerExpectation : XCTestExpectation - -@property(nonatomic, nullable) os_log_t log; -@property(nonatomic) os_log_type_t type; -@property(nonatomic) NSString *message; - -- (instancetype)initWithLog:(nullable os_log_t)log - type:(os_log_type_t)type - message:(NSString *)message; -@end - -@implementation GULOSLoggerExpectation -- (instancetype)initWithLog:(nullable os_log_t)log - type:(os_log_type_t)type - message:(NSString *)message { - self = [super - initWithDescription:[NSString - stringWithFormat:@"os_log_with_type(%@, %iu, %@) was not called.", - log, type, message]]; - if (self) { - _log = log; - _type = type; - _message = message; - } - return self; -} -@end - -// List of expectations that may be fulfilled in the current test. -static NSMutableArray *sExpectations; - -// Function that will be called by GULOSLogger instead of os_log_with_type. -void GULTestOSLogWithType(os_log_t log, os_log_type_t type, char *s, ...) { - // Grab the first variable argument. - va_list args; - va_start(args, s); - NSString *message = [NSString stringWithUTF8String:va_arg(args, char *)]; - va_end(args); - - // Look for an expectation that meets these parameters. - for (GULOSLoggerExpectation *expectation in sExpectations) { - if ((expectation.log == nil || expectation.log == log) && expectation.type == type && - [message containsString:expectation.message]) { - [expectation fulfill]; - return; // Only fulfill one expectation per call. - } - } -} - -#pragma mark - - -// Redefine class property as readwrite for testing. -@interface GULLogger (ForTesting) -@property(nonatomic, nullable, class, readwrite) id logger; -@end - -// Surface osLog and dispatchQueues for tests. -@interface GULOSLogger (ForTesting) -@property(nonatomic) NSMutableDictionary *categoryLoggers; -@property(nonatomic) dispatch_queue_t dispatchQueue; -@property(nonatomic, unsafe_unretained) void (*logFunction)(os_log_t, os_log_type_t, char *, ...); -@end - -#pragma mark - - -@interface GULOSLoggerTest : XCTestCase -@property(nonatomic, nullable) GULOSLogger *osLogger; -@property(nonatomic, nullable) id mock; -@property(nonatomic) BOOL appStoreWasSwizzled; -@end - -@implementation GULOSLoggerTest - -- (void)setAppStoreTo:(BOOL)fromAppStore { - [GULSwizzler swizzleClass:[GULAppEnvironmentUtil class] - selector:@selector(isFromAppStore) - isClassSelector:YES - withBlock:^BOOL() { - return fromAppStore; - }]; - self.appStoreWasSwizzled = YES; -} - -- (void)partialMockLogger { - // Add the ability to intercept calls to the instance under test - self.mock = OCMPartialMock(self.osLogger); - GULLogger.logger = self.mock; -} - -- (void)setUp { - [super setUp]; - // Setup globals and create the instance under test. - sExpectations = [[NSMutableArray alloc] init]; - self.osLogger = [[GULOSLogger alloc] init]; - self.osLogger.logFunction = &GULTestOSLogWithType; -} - -- (void)tearDown { - // Clear globals - sExpectations = nil; - GULLogger.logger = nil; - [self.mock stopMocking]; - self.mock = nil; - if (self.appStoreWasSwizzled) { - [GULSwizzler unswizzleClass:[GULAppEnvironmentUtil class] - selector:@selector(isFromAppStore) - isClassSelector:YES]; - self.appStoreWasSwizzled = NO; - } - [super tearDown]; -} - -#pragma mark Tests - -- (void)testInit { - // First, there are no loggers created. - XCTAssertNil(self.osLogger.categoryLoggers); - - // After initializeLogger, there should be an empty dictionary ready, for loggers. - [self.osLogger initializeLogger]; - NSDictionary *loggers = self.osLogger.categoryLoggers; - XCTAssertNotNil(loggers); - - // Calling initializeLogger logger again, should change the dictionary instance. - [self.osLogger initializeLogger]; - XCTAssertEqual(loggers, self.osLogger.categoryLoggers); -} - -- (void)testSetLogLevelValid { - // Setting the log level to something valid should not result in an error message. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wformat-security" - OCMReject([self.mock logWithLevel:GULLoggerLevelError - withService:OCMOCK_ANY - isForced:NO - withCode:OCMOCK_ANY - withMessage:OCMOCK_ANY]); -#pragma clang diagnostic pop - self.osLogger.logLevel = GULLoggerLevelWarning; - OCMVerifyAll(self.mock); -} - -- (void)testSetLogLevelInvalid { - // The logger should log an error for invalid levels. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wformat-security" - OCMExpect([[self.mock stub] logWithLevel:GULLoggerLevelError - withService:OCMOCK_ANY - isForced:YES - withCode:OCMOCK_ANY - withMessage:OCMOCK_ANY]); - self.osLogger.logLevel = GULLoggerLevelMin - 1; - - OCMExpect([[self.mock stub] logWithLevel:GULLoggerLevelError - withService:OCMOCK_ANY - isForced:YES - withCode:OCMOCK_ANY - withMessage:OCMOCK_ANY]); -#pragma clang diagnostic push - self.osLogger.logLevel = GULLoggerLevelMax + 1; - OCMVerifyAll(self.mock); -} - -- (void)testLogLevelAppStore { - // When not from the App Store, all log levels should be allowed. - [self setAppStoreTo:NO]; - self.osLogger.logLevel = GULLoggerLevelMin; - XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelMin); - self.osLogger.logLevel = GULLoggerLevelMax; - XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelMax); - - // When from the App store, levels that are Notice or above, should be silently ignored. - [self setAppStoreTo:YES]; - self.osLogger.logLevel = GULLoggerLevelError; - XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelError); - self.osLogger.logLevel = GULLoggerLevelWarning; - XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning); - self.osLogger.logLevel = GULLoggerLevelNotice; - XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning); - self.osLogger.logLevel = GULLoggerLevelInfo; - XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning); - self.osLogger.logLevel = GULLoggerLevelDebug; - XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning); -} - -- (void)testForceDebug { - [self partialMockLogger]; - [self setAppStoreTo:NO]; - XCTAssertFalse(self.osLogger.forcedDebug); - GULLoggerForceDebug(); - XCTAssertTrue(self.osLogger.forcedDebug); - XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelDebug); -} - -- (void)testForceDebugAppStore { - [self partialMockLogger]; - [self setAppStoreTo:YES]; - self.osLogger.logLevel = GULLoggerLevelWarning; - XCTAssertFalse(self.osLogger.forcedDebug); - GULLoggerForceDebug(); - XCTAssertFalse(self.osLogger.forcedDebug); - XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning); -} - -- (void)testLoggingValidNoVarArgs { - [self.osLogger initializeLogger]; - XCTAssert(self.osLogger.categoryLoggers.count == 0); - NSString *message = [NSUUID UUID].UUIDString; - GULOSLoggerExpectation *expectation = - [[GULOSLoggerExpectation alloc] initWithLog:nil type:OS_LOG_TYPE_DEFAULT message:message]; - [sExpectations addObject:expectation]; - [self.osLogger logWithLevel:GULLoggerLevelNotice - withService:kService - isForced:NO - withCode:kCode - withMessage:message]; - [self waitForExpectations:sExpectations timeout:kTimeout]; -} - -- (void)testLoggingValidWithVarArgs { - [self.osLogger initializeLogger]; - XCTAssert(self.osLogger.categoryLoggers.count == 0); - NSString *message = [NSUUID UUID].UUIDString; - GULOSLoggerExpectation *expectation = - [[GULOSLoggerExpectation alloc] initWithLog:nil type:OS_LOG_TYPE_DEFAULT message:message]; - [sExpectations addObject:expectation]; - [self.osLogger logWithLevel:GULLoggerLevelNotice - withService:kService - isForced:NO - withCode:kCode - withMessage:@"%@", message]; - [self waitForExpectations:sExpectations timeout:kTimeout]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Example/Tests/Network/third_party/LICENSE b/GoogleUtilities/Example/Tests/Network/third_party/LICENSE new file mode 100644 index 00000000000..bbc55fc8c82 --- /dev/null +++ b/GoogleUtilities/Example/Tests/Network/third_party/LICENSE @@ -0,0 +1,30 @@ +Based a little on HTTPServer, part of the CocoaHTTPServer sample code found at +https://opensource.apple.com/source/HTTPServer/HTTPServer-11/CocoaHTTPServer/ +License for the CocoaHTTPServer sample code: + +Software License Agreement (BSD License) + +Copyright (c) 2011, Deusty, LLC +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Neither the name of Deusty nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m index 4eb1cbc9cdf..5356fff334f 100644 --- a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m @@ -60,7 +60,17 @@ @interface GULTestAppDelegate : UIResponder { /** A URL property that is set by the app delegate methods, which is then used to verify if the app * delegate methods were properly called. */ -@property(nonatomic, copy) NSString *url; +@property(nonatomic, copy) NSURL *url; + +@property(nonatomic, strong) NSData *remoteNotificationsDeviceToken; +@property(nonatomic, strong) NSError *failToRegisterForRemoteNotificationsError; +@property(nonatomic, strong) NSDictionary *remoteNotification; +@property(nonatomic, copy) void (^remoteNotificationCompletionHandler)(UIBackgroundFetchResult); + +/** + * The application is set each time a UIApplicationDelegate method is called + */ +@property(nonatomic, weak) UIApplication *application; @end @@ -120,7 +130,8 @@ - (instancetype)init { - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { - _url = [url copy]; + self.application = app; + self.url = url; _isOpenURLOptionsMethodCalled = YES; return NO; } @@ -128,9 +139,39 @@ - (BOOL)application:(UIApplication *)app - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(nonnull NSString *)identifier completionHandler:(nonnull void (^)(void))completionHandler { + self.application = application; _backgroundSessionID = [identifier copy]; } +- (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + self.application = application; + self.remoteNotificationsDeviceToken = deviceToken; +} + +- (void)application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + self.application = application; + self.failToRegisterForRemoteNotificationsError = error; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo { + self.application = application; + self.remoteNotification = userInfo; +} +#pragma clang diagnostic pop + +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + self.application = application; + self.remoteNotification = userInfo; + self.remoteNotificationCompletionHandler = completionHandler; +} + // These are methods to test whether changing the class still maintains behavior that the app // delegate proxy shouldn't have modified. @@ -169,6 +210,7 @@ - (BOOL)application:(UIApplication *)app return YES; } +#if TARGET_OS_IOS - (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url sourceApplication:(nullable NSString *)sourceApplication @@ -176,6 +218,7 @@ - (BOOL)application:(UIApplication *)application _URLForIOS8 = [url copy]; return YES; } +#endif // TARGET_OS_IOS #if SDK_HAS_USERACTIVITY @@ -199,6 +242,13 @@ @implementation GULAppDelegateSwizzlerTest - (void)tearDown { [GULAppDelegateSwizzler clearInterceptors]; + [super tearDown]; +} + +- (void)testNotAppDelegateIsNotSwizzled { + NSObject *notAppDelegate = [[NSObject alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:(id)notAppDelegate]; + XCTAssertEqualObjects(NSStringFromClass([notAppDelegate class]), @"NSObject"); } /** Tests proxying an object that responds to UIApplicationDelegate protocol and makes sure that @@ -234,14 +284,27 @@ - (void)testProxyAppDelegate { XCTAssertEqual(sizeBefore, sizeAfter); // After being proxied, it should be able to respond to the required method selector. +#if TARGET_OS_IOS XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]); +#endif // TARGET_OS_IOS + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: continueUserActivity:restorationHandler:)]); XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]); XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: handleEventsForBackgroundURLSession:completionHandler:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + didReceiveRemoteNotification:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); + // Make sure that the class has changed. XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore); @@ -457,6 +520,7 @@ - (void)testResultOfApplicationOpenURLOptionsIsORed { XCTAssertTrue(shouldOpen); } +#if TARGET_OS_IOS /** Tests that application:openURL:sourceApplication:annotation: is invoked on the interceptors if * it exists. */ @@ -539,6 +603,7 @@ - (void)testApplicationOpenURLSourceApplicationAnnotationResultIsORed { // The result is YES if one of the interceptors returns YES. XCTAssertTrue(shouldOpen); } +#endif // TARGET_OS_IOS /** Tests that application:handleEventsForBackgroundURLSession:completionHandler: is invoked on the * interceptors if it exists. @@ -647,6 +712,131 @@ - (void)testApplicationContinueUserActivityRestorationHandlerResultsAreORed { XCTAssertTrue(shouldContinueUserActvitiy); } +- (void)testApplicationDidRegisterForRemoteNotificationsIsInvokedOnInterceptors { + NSData *deviceToken = [NSData data]; + UIApplication *application = [UIApplication sharedApplication]; + + id interceptor = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]); + + id interceptor2 = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor2 application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:testAppDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.remoteNotificationsDeviceToken, deviceToken); +} + +- (void)testApplicationDidFailToRegisterForRemoteNotificationsIsInvokedOnInterceptors { + NSError *error = [NSError errorWithDomain:@"test" code:-1 userInfo:nil]; + UIApplication *application = [UIApplication sharedApplication]; + + id interceptor = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor application:application + didFailToRegisterForRemoteNotificationsWithError:error]); + + id interceptor2 = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor2 application:application + didFailToRegisterForRemoteNotificationsWithError:error]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:testAppDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application didFailToRegisterForRemoteNotificationsWithError:error]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.failToRegisterForRemoteNotificationsError, error); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors { + NSDictionary *notification = @{}; + UIApplication *application = [UIApplication sharedApplication]; + + id interceptor = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor application:application didReceiveRemoteNotification:notification]); + + id interceptor2 = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor2 application:application didReceiveRemoteNotification:notification]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:testAppDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application didReceiveRemoteNotification:notification]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.remoteNotification, notification); +} +#pragma clang diagnostic pop + +- (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInterceptors { + NSDictionary *notification = @{}; + UIApplication *application = [UIApplication sharedApplication]; + void (^completion)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) { + }; + + id interceptor = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:completion]); + + id interceptor2 = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor2 application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:completion]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:testAppDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:completion]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.remoteNotification, notification); + XCTAssertEqual(testAppDelegate.remoteNotificationCompletionHandler, completion); +} + +- (void)testApplicationDidReceiveRemoteNotificationWithCompletionImplementationIsNotAdded { + // The delegate without application:didReceiveRemoteNotification:fetchCompletionHandler: + // implementation + GULTestInterceptorAppDelegate *legacyDelegate = [[GULTestInterceptorAppDelegate alloc] init]; + + XCTAssertFalse([legacyDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); + + [GULAppDelegateSwizzler proxyAppDelegate:legacyDelegate]; + + XCTAssertFalse([legacyDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); +} + #pragma mark - Tests to test that Plist flag is honored /** Tests that app delegate proxy is enabled when there is no Info.plist dictionary. */ diff --git a/GoogleUtilities/Logger/GULASLLogger.m b/GoogleUtilities/Logger/GULASLLogger.m deleted file mode 100644 index 51d6c03bff8..00000000000 --- a/GoogleUtilities/Logger/GULASLLogger.m +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "GULASLLogger.h" - -#import - -#import "GULAppEnvironmentUtil.h" -#import "GULLogger+Internal.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface GULASLLogger () { - GULLoggerLevel _logLevel; -} - -@property(nonatomic) aslclient aslClient; -@property(nonatomic) dispatch_queue_t dispatchQueue; - -@end - -@implementation GULASLLogger - -@synthesize forcedDebug = _forcedDebug; -@synthesize logLevel = _logLevel; -@synthesize version = _version; - -- (instancetype)init { - self = [super init]; - if (self) { - _forcedDebug = NO; - _logLevel = GULLoggerLevelNotice; - _version = @""; - _dispatchQueue = dispatch_queue_create("GULLoggerQueue", DISPATCH_QUEUE_SERIAL); - dispatch_set_target_queue(_dispatchQueue, - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); - } - return self; -} - -- (void)initializeLogger { - dispatch_sync(self.dispatchQueue, ^{ - if (!self.aslClient) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated - self.aslClient = asl_open(NULL, kGULLoggerClientFacilityName, ASL_OPT_STDERR); - asl_set_filter(self.aslClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE)); -#pragma clang diagnostic pop - } - }); -} - -- (void)dealloc { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated - asl_free(self.aslClient); -#pragma clang diagnostic pop -} - -- (void)setLogLevel:(GULLoggerLevel)logLevel { - if (logLevel < GULLoggerLevelMin || logLevel > GULLoggerLevelMax) { - GULLogError(kGULLoggerName, YES, kGULLoggerInvalidLoggerLevelCore, - kGULLoggerInvalidLoggerLevelMessage, (long)logLevel); - } - - // We should not raise the logger level if we are running from App Store. - if (logLevel >= GULLoggerLevelNotice && [GULAppEnvironmentUtil isFromAppStore]) { - return; - } - - // Ignore setting the level if forcedDebug is on. - if (self.forcedDebug) { - return; - } - - _logLevel = logLevel; -} - -- (GULLoggerLevel)logLevel { - return _logLevel; -} - -- (void)setForcedDebug:(BOOL)forcedDebug { - // We should not enable debug mode if we're running from App Store. - if (![GULAppEnvironmentUtil isFromAppStore]) { - if (forcedDebug) { - self.logLevel = GULLoggerLevelDebug; - } - _forcedDebug = forcedDebug; - } -} - -- (BOOL)forcedDebug { - return _forcedDebug; -} - -- (BOOL)isLoggableLevel:(GULLoggerLevel)logLevel { - return [GULLogger loggerSystem:self shouldLogMessageOfLevel:logLevel]; -} - -- (void)printToSTDERR { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated - asl_add_log_file(self.aslClient, STDERR_FILENO); -#pragma clang diagnostic pop -} - -- (void)logWithLevel:(GULLoggerLevel)level - withService:(GULLoggerService)service - isForced:(BOOL)forced - withCode:(NSString *)messageCode - withMessage:(NSString *)message, ... { - // Skip logging this if the level isn't to be logged unless it's forced. - if (![self isLoggableLevel:level] && !forced) { - return; - } - [self initializeLogger]; - - // Process the va_list here, while the parameters are on the stack. - va_list args; - va_start(args, message); - message = [[NSString alloc] initWithFormat:message arguments:args]; - va_end(args); - const char *logMsg = [GULLogger messageFromLogger:self - withService:service - code:messageCode - message:message] - .UTF8String; - dispatch_async(self.dispatchQueue, ^{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated - asl_log(self.aslClient, NULL, (int)level, "%s", logMsg); -#pragma clang diagnostic pop - }); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Logger/GULLogger+Internal.h b/GoogleUtilities/Logger/GULLogger+Internal.h deleted file mode 100644 index ce1b12b1cb7..00000000000 --- a/GoogleUtilities/Logger/GULLogger+Internal.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "GULLogger.h" - -#import "GULLoggerSystem.h" - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const kGULLoggerInvalidLoggerLevelCore = @"I-COR000023"; -static NSString *const kGULLoggerInvalidLoggerLevelMessage = @"Invalid logger level, %ld"; -static GULLoggerService const kGULLoggerName = @"[GULLogger]"; -static const char *const kGULLoggerClientFacilityName = "com.google.utilities.logger"; - -@interface GULLogger (Internal) - -/** - * Checks to see if the given logger system would log a message of a given level. - * - * @param logger The logger that may be logging a message. - * @param logLevel The level of the message that may be logged. - * @return YES if the given logger should be logging a message of the given level. - */ -+ (BOOL)loggerSystem:(id)logger shouldLogMessageOfLevel:(GULLoggerLevel)logLevel; - -/** - * Formats the given message, code and service for output by the given logger system. - * - * @param logger The logger which will output this message - * @param service The service sending the message to the logger - * @param code The code for this message - * @param message The log message, optionally a format string - * @return A completed string, ready to be output by a logger system. - */ -+ (NSString *)messageFromLogger:(id)logger - withService:(GULLoggerService)service - code:(NSString *)code - message:(NSString *)message; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Logger/GULLogger.m b/GoogleUtilities/Logger/GULLogger.m index b5d5e6804a7..495e5830bb0 100644 --- a/GoogleUtilities/Logger/GULLogger.m +++ b/GoogleUtilities/Logger/GULLogger.m @@ -12,86 +12,134 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "GULLogger.h" +#import "Private/GULLogger.h" -#import "GULASLLogger.h" -#import "GULAppEnvironmentUtil.h" -#import "GULLogger+Internal.h" -#import "GULLoggerLevel.h" -#import "GULOSLogger.h" +#include -#if TARGET_OS_IOS -#import -#endif +#import +#import "Public/GULLoggerLevel.h" + +/// ASL client facility name used by GULLogger. +const char *kGULLoggerASLClientFacilityName = "com.google.utilities.logger"; + +static dispatch_once_t sGULLoggerOnceToken; + +static aslclient sGULLoggerClient; + +static dispatch_queue_t sGULClientQueue; + +static BOOL sGULLoggerDebugMode; + +static GULLoggerLevel sGULLoggerMaximumLevel; + +// Allow clients to register a version to include in the log. +static const char *sVersion = ""; + +static GULLoggerService kGULLoggerLogger = @"[GULLogger]"; #ifdef DEBUG +/// The regex pattern for the message code. +static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$"; static NSRegularExpression *sMessageCodeRegex; -static NSString *const kGULLoggerMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$"; #endif -@implementation GULLogger (Internal) +void GULLoggerInitializeASL(void) { + dispatch_once(&sGULLoggerOnceToken, ^{ + NSInteger majorOSVersion = [[GULAppEnvironmentUtil systemVersion] integerValue]; + uint32_t aslOptions = ASL_OPT_STDERR; +#if TARGET_OS_SIMULATOR + // The iOS 11 simulator doesn't need the ASL_OPT_STDERR flag. + if (majorOSVersion >= 11) { + aslOptions = 0; + } +#else + // Devices running iOS 10 or higher don't need the ASL_OPT_STDERR flag. + if (majorOSVersion >= 10) { + aslOptions = 0; + } +#endif // TARGET_OS_SIMULATOR -+ (BOOL)loggerSystem:(id)logger shouldLogMessageOfLevel:(GULLoggerLevel)logLevel { - if (logger.forcedDebug) { - return YES; - } else if (logLevel < GULLoggerLevelMin || logLevel > GULLoggerLevelMax) { - return NO; - } - return logLevel <= logger.logLevel; -} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated + // Initialize the ASL client handle. + sGULLoggerClient = asl_open(NULL, kGULLoggerASLClientFacilityName, aslOptions); + sGULLoggerMaximumLevel = GULLoggerLevelNotice; -+ (NSString *)messageFromLogger:(id)logger - withService:(GULLoggerService)service - code:(NSString *)code - message:(NSString *)message { + // Set the filter used by system/device log. Initialize in default mode. + asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE)); + + sGULClientQueue = dispatch_queue_create("GULLoggingClientQueue", DISPATCH_QUEUE_SERIAL); + dispatch_set_target_queue(sGULClientQueue, + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); #ifdef DEBUG - NSCAssert(code.length == 11, @"Incorrect message code length."); - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sMessageCodeRegex = - [NSRegularExpression regularExpressionWithPattern:kGULLoggerMessageCodePattern - options:0 - error:NULL]; - }); - NSRange messageCodeRange = NSMakeRange(0, code.length); - NSUInteger numberOfMatches = [sMessageCodeRegex numberOfMatchesInString:code + sMessageCodeRegex = [NSRegularExpression regularExpressionWithPattern:kMessageCodePattern options:0 - range:messageCodeRange]; - NSCAssert(numberOfMatches == 1, @"Incorrect message code format."); + error:NULL]; #endif - return [NSString stringWithFormat:@"%@ - %@[%@] %@", logger.version, service, code, message]; + }); } -@end +void GULLoggerEnableSTDERR(void) { + asl_add_log_file(sGULLoggerClient, STDERR_FILENO); +} -static id sGULLogger; +void GULLoggerForceDebug(void) { + // We should enable debug mode if we're not running from App Store. + if (![GULAppEnvironmentUtil isFromAppStore]) { + sGULLoggerDebugMode = YES; + GULSetLoggerLevel(GULLoggerLevelDebug); + } +} -void GULLoggerInitialize(void) { - [GULLogger.logger initializeLogger]; +__attribute__((no_sanitize("thread"))) void GULSetLoggerLevel(GULLoggerLevel loggerLevel) { + if (loggerLevel < GULLoggerLevelMin || loggerLevel > GULLoggerLevelMax) { + GULLogError(kGULLoggerLogger, NO, @"I-COR000023", @"Invalid logger level, %ld", + (long)loggerLevel); + return; + } + GULLoggerInitializeASL(); + // We should not raise the logger level if we are running from App Store. + if (loggerLevel >= GULLoggerLevelNotice && [GULAppEnvironmentUtil isFromAppStore]) { + return; + } + + sGULLoggerMaximumLevel = loggerLevel; + dispatch_async(sGULClientQueue, ^{ + asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel)); + }); } -void GULLoggerInitializeASL(void) { - GULLoggerInitialize(); +/** + * Check if the level is high enough to be loggable. + */ +__attribute__((no_sanitize("thread"))) BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel) { + GULLoggerInitializeASL(); + if (sGULLoggerDebugMode) { + return YES; + } + return (BOOL)(loggerLevel <= sGULLoggerMaximumLevel); } -void GULLoggerEnableSTDERR(void) { - [GULLogger.logger printToSTDERR]; +#ifdef DEBUG +void GULResetLogger() { + sGULLoggerOnceToken = 0; } -void GULLoggerForceDebug(void) { - GULLogger.logger.forcedDebug = YES; +aslclient getGULLoggerClient() { + return sGULLoggerClient; } -void GULSetLoggerLevel(GULLoggerLevel loggerLevel) { - GULLogger.logger.logLevel = loggerLevel; +dispatch_queue_t getGULClientQueue() { + return sGULClientQueue; } -BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel) { - return [GULLogger.logger isLoggableLevel:loggerLevel]; +BOOL getGULLoggerDebugMode() { + return sGULLoggerDebugMode; } +#endif void GULLoggerRegisterVersion(const char *version) { - GULLogger.logger.version = [NSString stringWithUTF8String:version]; + sVersion = version; } void GULLogBasic(GULLoggerLevel level, @@ -99,16 +147,27 @@ void GULLogBasic(GULLoggerLevel level, BOOL forceLog, NSString *messageCode, NSString *message, - ...) { - va_list formatArgs; - va_start(formatArgs, message); - [GULLogger.logger logWithLevel:level - withService:service - isForced:forceLog - withCode:messageCode - withMessage:messageCode, formatArgs]; - va_end(formatArgs); + va_list args_ptr) { + GULLoggerInitializeASL(); + if (!(level <= sGULLoggerMaximumLevel || sGULLoggerDebugMode || forceLog)) { + return; + } + +#ifdef DEBUG + NSCAssert(messageCode.length == 11, @"Incorrect message code length."); + NSRange messageCodeRange = NSMakeRange(0, messageCode.length); + NSUInteger numberOfMatches = [sMessageCodeRegex numberOfMatchesInString:messageCode + options:0 + range:messageCodeRange]; + NSCAssert(numberOfMatches == 1, @"Incorrect message code format."); +#endif + NSString *logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr]; + logMsg = [NSString stringWithFormat:@"%s - %@[%@] %@", sVersion, service, messageCode, logMsg]; + dispatch_async(sGULClientQueue, ^{ + asl_log(sGULLoggerClient, NULL, level, "%s", logMsg.UTF8String); + }); } +#pragma clang diagnostic pop /** * Generates the logging functions using macros. @@ -135,39 +194,16 @@ void GULLogBasic(GULLoggerLevel level, #undef GUL_MAKE_LOGGER -// Redefine logger property as readwrite as a form of dependency injection. -@interface GULLogger () -@property(nonatomic, nullable, class, readwrite) id logger; -@end +#pragma mark - GULLoggerWrapper -@implementation GULLogger - -+ (id)logger { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // Synchronize here to avoid undefined behaviour if a setLogger: call happened before the - // first get. - @synchronized(self) { - if (!sGULLogger) { -#if __has_builtin(__builtin_available) - if (@available(iOS 9.0, *)) { -#else - if ([[UIDevice currentDevice].systemVersion integerValue] >= 9) { -#endif - sGULLogger = [[GULOSLogger alloc] init]; - } else { - sGULLogger = [[GULASLLogger alloc] init]; - } - } - } - }); - return sGULLogger; -} +@implementation GULLoggerWrapper -+ (void)setLogger:(nullable id)logger { - @synchronized(self) { - sGULLogger = logger; - } ++ (void)logWithLevel:(GULLoggerLevel)level + withService:(GULLoggerService)service + withCode:(NSString *)messageCode + withMessage:(NSString *)message + withArgs:(va_list)args { + GULLogBasic(level, service, NO, messageCode, message, args); } @end diff --git a/GoogleUtilities/Logger/GULOSLogger.m b/GoogleUtilities/Logger/GULOSLogger.m deleted file mode 100644 index 388db6af9cd..00000000000 --- a/GoogleUtilities/Logger/GULOSLogger.m +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "GULOSLogger.h" - -#import - -#import "GULAppEnvironmentUtil.h" -#import "GULLogger+Internal.h" -#import "GULLoggerLevel.h" - -#if TARGET_OS_IOS -#import -#endif - -NS_ASSUME_NONNULL_BEGIN - -// Function which calls the macro so that this can be substituted for testing. -// Since the macro enforces built-in constant-ness of the format string, it is replaced by "s" -// and the va_list should only contain one argument, a full message with format substitutions -// already filled. -static void GULLOSLogWithType(os_log_t log, os_log_type_t type, char *format, ...) { -#if __has_builtin(__builtin_available) - if (@available(iOS 9.0, *)) { -#else - if ([[UIDevice currentDevice].systemVersion integerValue] >= 9) { -#endif - va_list args; - va_start(args, format); - NSString *formattedString = - [[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:args]; -#if TARGET_OS_OSX - // Silence macOS 10.10 warning until we move minimum to 10.11. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - os_log_with_type(log, type, "%s", [formattedString UTF8String]); -#pragma clang diagnostic pop -#else - os_log_with_type(log, type, "%s", [formattedString UTF8String]); -#endif - va_end(args); - } else { -#ifdef DEBUG - NSCAssert(NO, @"Attempting to use os_log on iOS version prior to 9."); -#endif - } -} - -@interface GULOSLogger () - -// Dictionary of Category -> os_log instances. -@property(nonatomic) NSMutableDictionary *categoryLoggers; - -// The dispatch queue used to asynchronously call to os_log. -@property(nonatomic) dispatch_queue_t dispatchQueue; - -// This property is a function pointer to the method that logs messages to os_log. -// This indirection allows us to inject a different function pointer for dependency injection. -@property(nonatomic, unsafe_unretained) void (*logFunction)(os_log_t, os_log_type_t, char *, ...); - -@end - -@implementation GULOSLogger - -// Auto-synthesis not available for these since they are defined in the protocol. -@synthesize forcedDebug = _forcedDebug; -@synthesize logLevel = _logLevel; -@synthesize version = _version; - -- (instancetype)init { - self = [super init]; - if (self) { - _forcedDebug = NO; - _logLevel = // When running from an App Store build, avoid noisy levels below Warning. - [GULAppEnvironmentUtil isFromAppStore] ? GULLoggerLevelWarning : GULLoggerLevelNotice; - _version = @""; - _dispatchQueue = dispatch_queue_create("GULLoggerQueue", DISPATCH_QUEUE_SERIAL); - _logFunction = &GULLOSLogWithType; - dispatch_set_target_queue(_dispatchQueue, - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); - } - return self; -} - -- (void)initializeLogger { - dispatch_sync(self.dispatchQueue, ^{ - if (!self.categoryLoggers) { - self.categoryLoggers = [[NSMutableDictionary alloc] init]; - } - }); -} - -- (void)setLogLevel:(GULLoggerLevel)logLevel { - if (logLevel < GULLoggerLevelMin || logLevel > GULLoggerLevelMax) { - GULLogError(kGULLoggerName, YES, kGULLoggerInvalidLoggerLevelCore, - kGULLoggerInvalidLoggerLevelMessage, (long)logLevel); - } - - // We should not raise the logger level if we are running from App Store. - if (logLevel >= GULLoggerLevelNotice && [GULAppEnvironmentUtil isFromAppStore]) { - return; - } - - // Ignore setting the level if forcedDebug is on. - if (self.forcedDebug) { - return; - } - - _logLevel = logLevel; -} - -- (GULLoggerLevel)logLevel { - return _logLevel; -} - -- (void)setForcedDebug:(BOOL)forcedDebug { - // We should not enable debug mode if we're running from App Store. - if (![GULAppEnvironmentUtil isFromAppStore]) { - if (forcedDebug) { - self.logLevel = GULLoggerLevelDebug; - } - _forcedDebug = forcedDebug; - } -} - -- (BOOL)forcedDebug { - return _forcedDebug; -} - -- (void)printToSTDERR { - // NO-OP - os_log always outputs to STDERR and cannot be turned off. - // See http://www.openradar.me/36919139 -} - -- (BOOL)isLoggableLevel:(GULLoggerLevel)logLevel { - return [GULLogger loggerSystem:self shouldLogMessageOfLevel:logLevel]; -} - -- (void)logWithLevel:(GULLoggerLevel)level - withService:(GULLoggerService)service - isForced:(BOOL)forced - withCode:(NSString *)messageCode - withMessage:(NSString *)message, ... { - // Skip logging this if the level isn't to be logged unless it's forced. - if (![self isLoggableLevel:level] && !forced) { - return; - } - [self initializeLogger]; - - // Process the va_list here, while the parameters are on the stack. - va_list args; - va_start(args, message); - NSString *completeMessage = [[NSString alloc] initWithFormat:message arguments:args]; - va_end(args); - completeMessage = [GULLogger messageFromLogger:self - withService:service - code:messageCode - message:[completeMessage copy]]; - - // Avoid blocking during logging. - dispatch_async(self.dispatchQueue, ^{ - os_log_t osLog = self.categoryLoggers[service]; - if (!osLog) { -#if __has_builtin(__builtin_available) - if (@available(iOS 9.0, *)) { -#else - if ([[UIDevice currentDevice].systemVersion integerValue] >= 9) { -#endif -#if TARGET_OS_OSX - // Silence macOS 10.10 warning until we move minimum to 10.11. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - osLog = os_log_create(kGULLoggerClientFacilityName, service.UTF8String); -#pragma clang diagnostic pop -#else - osLog = os_log_create(kGULLoggerClientFacilityName, service.UTF8String); -#endif - self.categoryLoggers[service] = osLog; - } else { -#ifdef DEBUG - NSCAssert(NO, @"Attempting to use os_log on iOS version prior to 9."); -#endif - } - } - // Call the function pointer using the message constructed by GULLogger. - (*self.logFunction)(osLog, [[self class] osLogTypeForGULLoggerLevel:level], "%s", - completeMessage.UTF8String); - }); -} - -+ (os_log_type_t)osLogTypeForGULLoggerLevel:(GULLoggerLevel)level { - switch (level) { - case GULLoggerLevelDebug: - return OS_LOG_TYPE_DEBUG; - case GULLoggerLevelInfo: - return OS_LOG_TYPE_INFO; - case GULLoggerLevelNotice: - case GULLoggerLevelWarning: - // Both Notice and Warning map to the os_log default level. - return OS_LOG_TYPE_DEFAULT; - case GULLoggerLevelError: - return OS_LOG_TYPE_ERROR; - default: - return OS_LOG_TYPE_DEFAULT; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Logger/Private/GULLogger.h b/GoogleUtilities/Logger/Private/GULLogger.h index ae8e6fb46be..ff425768681 100644 --- a/GoogleUtilities/Logger/Private/GULLogger.h +++ b/GoogleUtilities/Logger/Private/GULLogger.h @@ -16,27 +16,21 @@ #import -#import "GULLoggerSystem.h" +#import NS_ASSUME_NONNULL_BEGIN +/** + * The services used in the logger. + */ +typedef NSString *const GULLoggerService; + #ifdef __cplusplus extern "C" { #endif // __cplusplus -/** - * Initialize the default GULLogger. - * - * @discussion On iOS 9 and earlier. GULLogger will use ASL. For iOS 10 and later, os_log is used. - */ -extern void GULLoggerInitialize(void); - /** * Initialize GULLogger. - * - * @discussion This version should no longer be used. ASL is deprecated by Apple to be replaced by - * os_log. Calls to this function are redirected to GULLoggerInitialize to ensure - * functionality on all iOS versions. */ extern void GULLoggerInitializeASL(void); @@ -53,34 +47,33 @@ extern void GULLoggerEnableSTDERR(void); /** * Changes the default logging level of GULLoggerLevelNotice to a user-specified level. * The default level cannot be set above GULLoggerLevelNotice if the app is running from App Store. - * @param loggerLevel Log level (one of the GULLoggerLevel enum values). + * (required) log level (one of the GULLoggerLevel enum values). */ extern void GULSetLoggerLevel(GULLoggerLevel loggerLevel); /** * Checks if the specified logger level is loggable given the current settings. - * @param loggerLevel Log level (one of the GULLoggerLevel enum values). + * (required) log level (one of the GULLoggerLevel enum values). */ extern BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel); /** * Register version to include in logs. - * @param version The version to register with the logger. + * (required) version */ extern void GULLoggerRegisterVersion(const char *version); /** * Logs a message to the Xcode console and the device log. If running from AppStore, will * not log any messages with a level higher than GULLoggerLevelNotice to avoid log spamming. - * @param level Log level (one of the GULLoggerLevel enum values). - * @param service Service name of type GULLoggerService. - * @param forceLog If this message should be output regardless of its level. - * @param messageCode starting with "I-" which means iOS, followed by a capitalized + * (required) log level (one of the GULLoggerLevel enum values). + * (required) service name of type GULLoggerService. + * (required) message code starting with "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". - * @param message string which can be a format string. - * @param ... variable arguments list obtained from calling va_start, used when message is a format + * (required) message string which can be a format string. + * (optional) variable arguments list obtained from calling va_start, used when message is a format * string. */ extern void GULLogBasic(GULLoggerLevel level, @@ -88,20 +81,25 @@ extern void GULLogBasic(GULLoggerLevel level, BOOL forceLog, NSString *messageCode, NSString *message, - ...); +// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable +// See: http://stackoverflow.com/q/29095469 +#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX + va_list args_ptr +#else + va_list _Nullable args_ptr +#endif +); /** * The following functions accept the following parameters in order: - * @param service Name of type GULLoggerService. - * @param messageCode Starting from "I-" which means iOS, followed by a capitalized + * (required) service name of type GULLoggerService. + * (required) message code starting from "I-" which means iOS, followed by a capitalized * three-character service identifier and a six digit integer message ID that is unique * within the service. * An example of the message code is @"I-COR000001". * See go/firebase-log-proposal for details. - * @param message String which can be a format string. - * @param ... The list of arguments to substitute into the format string. - * - * @discussion + * (required) message string which can be a format string. + * (optional) the list of arguments to substitute into the format string. * Example usage: * GULLogError(kGULLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name); */ @@ -135,10 +133,26 @@ extern void GULLogDebug(GULLoggerService service, } // extern "C" #endif // __cplusplus -@interface GULLogger : NSObject +@interface GULLoggerWrapper : NSObject + +/** + * Objective-C wrapper for GULLogBasic to allow weak linking to GULLogger + * (required) log level (one of the GULLoggerLevel enum values). + * (required) service name of type GULLoggerService. + * (required) message code starting with "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * (required) message string which can be a format string. + * (optional) variable arguments list obtained from calling va_start, used when message is a format + * string. + */ -/// The current default logger. -@property(nonatomic, class, readonly) id logger; ++ (void)logWithLevel:(GULLoggerLevel)level + withService:(GULLoggerService)service + withCode:(NSString *)messageCode + withMessage:(NSString *)message + withArgs:(va_list)args; @end diff --git a/GoogleUtilities/Logger/Private/GULLoggerSystem.h b/GoogleUtilities/Logger/Private/GULLoggerSystem.h deleted file mode 100644 index f19bacadc1a..00000000000 --- a/GoogleUtilities/Logger/Private/GULLoggerSystem.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2019 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "GULLoggerLevel.h" - -NS_ASSUME_NONNULL_BEGIN - -/// The service name used by the logger. -typedef NSString *const GULLoggerService; - -/// This protocol describes a GoogleUtilities Logger System implementation. -@protocol GULLoggerSystem - -/// The current log level of this logger. Defaults to GULLoggerLevelNotice. -@property(nonatomic) GULLoggerLevel logLevel; - -/// The version to report to the logs. Defaults to the empty string. -@property(nonatomic) NSString *version; - -/// Forces the current log level to be set to debug. Defaults to NO. -@property(nonatomic) BOOL forcedDebug; - -/// Initializes the logger before use. -- (void)initializeLogger; - -/// Enables output to STDERR. Not enabled by default. -- (void)printToSTDERR; - -/// Checks to see if a given level would be logged given the current level of the logger. -- (BOOL)isLoggableLevel:(GULLoggerLevel)logLevel; - -/// Logs the given message. -- (void)logWithLevel:(GULLoggerLevel)level - withService:(GULLoggerService)service - isForced:(BOOL)forced - withCode:(NSString *)messageCode - withMessage:(NSString *)message, ... NS_FORMAT_FUNCTION(5, 6); -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilities/Logger/Public/GULLoggerLevel.h b/GoogleUtilities/Logger/Public/GULLoggerLevel.h index e7f5c3bfe5e..81ff212d716 100644 --- a/GoogleUtilities/Logger/Public/GULLoggerLevel.h +++ b/GoogleUtilities/Logger/Public/GULLoggerLevel.h @@ -14,26 +14,22 @@ * limitations under the License. */ -/// The log levels used by internal logging. +/** + * The log levels used by internal logging. + */ typedef NS_ENUM(NSInteger, GULLoggerLevel) { - /// Error level, matches ASL_LEVEL_ERR and is used for OS_LOG_TYPE_ERROR. + /** Error level, matches ASL_LEVEL_ERR. */ GULLoggerLevelError = 3, - - /// Warning level, matches ASL_LEVEL_WARNING and is used for OS_LOG_TYPE_DEFAULT. + /** Warning level, matches ASL_LEVEL_WARNING. */ GULLoggerLevelWarning = 4, - - /// Notice level, matches ASL_LEVEL_NOTICE and is used for OS_LOG_TYPE_DEFAULT. + /** Notice level, matches ASL_LEVEL_NOTICE. */ GULLoggerLevelNotice = 5, - - /// Info level, matches ASL_LEVEL_INFO and is used for OS_LOG_TYPE_INFO. + /** Info level, matches ASL_LEVEL_INFO. */ GULLoggerLevelInfo = 6, - - /// Debug level, matches ASL_LEVEL_DEBUG and is mapped to OS_LOG_TYPE_DEBUG. + /** Debug level, matches ASL_LEVEL_DEBUG. */ GULLoggerLevelDebug = 7, - - /// Minimum log level. + /** Minimum log level. */ GULLoggerLevelMin = GULLoggerLevelError, - - /// Maximum log level. + /** Maximum log level. */ GULLoggerLevelMax = GULLoggerLevelDebug } NS_SWIFT_NAME(GoogleLoggerLevel); diff --git a/InAppMessaging/Example/Podfile b/InAppMessaging/Example/Podfile index 04a75ebcee4..3931594447b 100644 --- a/InAppMessaging/Example/Podfile +++ b/InAppMessaging/Example/Podfile @@ -1,9 +1,11 @@ use_frameworks! # Uncomment the next two lines for pre-release testing on public repo -# source 'https://github.com/Firebase/SpecsStaging.git' -# source 'https://github.com/CocoaPods/Specs.git' +source 'https://github.com/Firebase/SpecsStaging.git' +source 'https://github.com/CocoaPods/Specs.git' +# Remove version from FirebaseAnalytics after 6.0 goes public. +pod 'FirebaseAnalytics', '6.0.0-pre' pod 'FirebaseCore', :path => '../..' target 'InAppMessaging_Example_iOS' do @@ -12,15 +14,15 @@ target 'InAppMessaging_Example_iOS' do pod 'FirebaseInAppMessagingDisplay', :path => '../..' pod 'FirebaseInAppMessaging', :path => '../..' pod 'FirebaseAnalyticsInterop', :path => '../..' - pod 'FirebaseAnalytics' pod 'FirebaseDynamicLinks', :path => '../..' + pod 'FirebaseInstanceID', :path => '../..' end target 'InAppMessaging_Tests_iOS' do platform :ios, '8.0' pod 'FirebaseInAppMessaging', :path => '../..' - pod 'FirebaseInstanceID' + pod 'FirebaseInstanceID', :path => '../..' pod 'FirebaseAnalyticsInterop', :path => '../..' pod 'OCMock' end diff --git a/InAppMessaging/Example/Tests/FIRIAMClearcutUploaderTests.m b/InAppMessaging/Example/Tests/FIRIAMClearcutUploaderTests.m index c4bc6b6725e..d26c37b4205 100644 --- a/InAppMessaging/Example/Tests/FIRIAMClearcutUploaderTests.m +++ b/InAppMessaging/Example/Tests/FIRIAMClearcutUploaderTests.m @@ -98,7 +98,7 @@ - (void)testUploadTriggeredWhenWaitTimeConditionSatisfied { [self waitForExpectationsWithTimeout:1.0 handler:nil]; } -- (void)testUploadNotTriggeredWhenWaitTimeConditionNotSatisfied { +- (void)disable_testUploadNotTriggeredWhenWaitTimeConditionNotSatisfied { // using a real storage in this case FIRIAMClearcutLogStorage *logStorage = [[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000 @@ -238,7 +238,7 @@ - (void)testRespectingWaitTimeFromRequestSender { XCTAssertEqual(currentMoment * 1000 + 1500, uploader.nextValidSendTimeInMills); } -- (void)testWaitTimeFromRequestSenderAdjustedByMinWaitTimeInStrategy { +- (void)disable_testWaitTimeFromRequestSenderAdjustedByMinWaitTimeInStrategy { // using a real storage in this case FIRIAMClearcutLogStorage *logStorage = [[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000 diff --git a/InAppMessagingDisplay/Example/Podfile b/InAppMessagingDisplay/Example/Podfile index 239b3e94570..6ee7a4020e0 100644 --- a/InAppMessagingDisplay/Example/Podfile +++ b/InAppMessagingDisplay/Example/Podfile @@ -1,6 +1,11 @@ +# Uncomment the next two lines for pre-release testing on public repo +source 'https://github.com/Firebase/SpecsStaging.git' +source 'https://github.com/CocoaPods/Specs.git' use_frameworks! +pod 'FirebaseCore', :path => '../..' + target 'FiamDisplaySwiftExample' do platform :ios, '8.0' pod 'FirebaseInAppMessagingDisplay', :path => '../..' diff --git a/Interop/Firebase Component System.md b/Interop/FirebaseComponentSystem.md similarity index 100% rename from Interop/Firebase Component System.md rename to Interop/FirebaseComponentSystem.md diff --git a/Metrics/.gitignore b/Metrics/.gitignore new file mode 100644 index 00000000000..02c087533d1 --- /dev/null +++ b/Metrics/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj diff --git a/Metrics/Package.resolved b/Metrics/Package.resolved new file mode 100644 index 00000000000..0e9231894bf --- /dev/null +++ b/Metrics/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "CommandLineKit", + "repositoryURL": "https://github.com/objecthub/swift-commandlinekit", + "state": { + "branch": null, + "revision": "b6c7786a8fbbe32a819474d3a85f8d93db63052c", + "version": "0.2.5" + } + } + ] + }, + "version": 1 +} diff --git a/Metrics/Package.swift b/Metrics/Package.swift new file mode 100644 index 00000000000..bbcd417c841 --- /dev/null +++ b/Metrics/Package.swift @@ -0,0 +1,39 @@ +// swift-tools-version:4.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import PackageDescription + +let package = Package( + name: "Metrics", + dependencies: [ + .package(url: "https://github.com/objecthub/swift-commandlinekit", from: "0.2.5"), + ], + targets: [ + .target( + name: "MetricsLib" + ), + .target( + name: "Metrics", + dependencies: ["CommandLineKit", "MetricsLib"] + ), + .testTarget( + name: "MetricsTests", + dependencies: ["MetricsLib"] + ), + ] +) diff --git a/Metrics/README.md b/Metrics/README.md new file mode 100644 index 00000000000..84949682e86 --- /dev/null +++ b/Metrics/README.md @@ -0,0 +1,20 @@ +# Metrics + +A swift utility for collecting project health metrics on Travis and uploading to a database. It +currently only supports parsing a Code Coverage report generated from XCov. + +## Run the coverage parser + +``` +swift build +.build/debug/Metrics -c=example_report.json -o=database.json -p=99 +``` + +This generates a database.json file that can be executed through a pre-existing Java uploader that +will push the data to a Cloud SQL database. + +## Run the unit tests + +``` +swift test +``` diff --git a/Metrics/Sources/Metrics/main.swift b/Metrics/Sources/Metrics/main.swift new file mode 100644 index 00000000000..194d2a46a3d --- /dev/null +++ b/Metrics/Sources/Metrics/main.swift @@ -0,0 +1,59 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import CommandLineKit +import Foundation +import MetricsLib + +var flags = Flags() +let coveragePath = flags.string("c", + "coverage", + description: "Required - The path of the JSON coverage report generated by XCov.") +let outputPath = flags.string("o", + "output", + description: "Required - The path to write the database JSON info.") +let pullRequest = flags.int("p", + "pull_request", + description: "Required - The number of the pull request that corresponds to this coverage run.") +do { + try flags.parse() + if !coveragePath.wasSet { + print("Please specify the path of the JSON coverage report from XCov. -c or --coverage") + exit(1) + } + if !outputPath.wasSet { + print("Please specify output location for the database JSON file. -o or --output") + exit(1) + } + if !pullRequest.wasSet { + print("Please specify the corresponding pull request number. -p or --pull_request") + exit(1) + } + let pullRequestTable = TableUpdate(table_name: "PullRequests", + column_names: ["pull_request_id"], + replace_measurements: [[Double(pullRequest.value!)]]) + let coverageReport = try CoverageReport.load(path: coveragePath.value!) + let coverageTable = TableUpdate.createFrom(coverage: coverageReport, + pullRequest: pullRequest.value!) + let json = try UploadMetrics(tables: [pullRequestTable, coverageTable]).json() + try json.write(to: NSURL(fileURLWithPath: outputPath.value!) as URL, + atomically: false, + encoding: .utf8) + print("Successfully created \(outputPath.value!)") +} catch { + print("Error occurred: \(error)") + exit(1) +} diff --git a/Metrics/Sources/MetricsLib/CoverageReport.swift b/Metrics/Sources/MetricsLib/CoverageReport.swift new file mode 100644 index 00000000000..a20b35621c0 --- /dev/null +++ b/Metrics/Sources/MetricsLib/CoverageReport.swift @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +/// Represents a code coverage report generated by XCov (maps to the JSON report one to one). +public struct CoverageReport: Decodable { + public var targets: [Target] + public var coverage: Double + + public init(targets: [Target], coverage: Double) { + self.targets = targets + self.coverage = coverage + } + + /// Creates a CoverageReport from a JSON file generated by XCov. + public static func load(path: String) throws -> CoverageReport { + let data = try Data(contentsOf: NSURL(fileURLWithPath: path) as URL) + let decoder = JSONDecoder() + return try decoder.decode(CoverageReport.self, from: data) + } +} + +/// An XCov target. +public struct Target: Decodable { + public var name: String + public var files: [File] + public var coverage: Double + + public init(name: String, coverage: Double) { + self.name = name + self.coverage = coverage + files = [] + } +} + +/// An XCov file. +public struct File: Decodable { + public var name: String + public var coverage: Double + public var type: String + public var functions: [Function] +} + +/// An XCov function. +public struct Function: Decodable { + public var name: String + public var coverage: Double +} diff --git a/Metrics/Sources/MetricsLib/UploadMetrics.swift b/Metrics/Sources/MetricsLib/UploadMetrics.swift new file mode 100644 index 00000000000..805a9fac4c4 --- /dev/null +++ b/Metrics/Sources/MetricsLib/UploadMetrics.swift @@ -0,0 +1,76 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +/// A mapping of SDKs to an ID that will represent that SDK in the database. +let TARGET_TO_SDK_INDEX = [ + "Auth_Example_iOS.app": 0.0, + "Core_Example_iOS.app": 1.0, + "Database_Example_iOS.app": 2.0, + "DynamicLinks_Example_iOS.app": 3.0, + "InstanceID_Example_iOS.app": 4.0, + "Messaging_Example_iOS.app": 5.0, + "Storage_Example_iOS.app": 6.0, + // TODO(Corrob): Add support for Firestore, Functions, and InAppMessaging. +] + +/// Represents a set of metric table updates to upload to the database. +public struct UploadMetrics: Encodable { + public var tables: [TableUpdate] + + public init(tables: [TableUpdate]) { + self.tables = tables + } + + /// Converts the metric table updates to a JSON format this is compatible with the Java uploader. + public func json() throws -> String { + let json = try JSONEncoder().encode(self) + return String(data: json, encoding: .utf8)! + } +} + +/// An update to a metrics table with the new data to uplaod to the database. +public struct TableUpdate: Encodable { + public var table_name: String + public var column_names: [String] + public var replace_measurements: [[Double]] + + public init(table_name: String, column_names: [String], replace_measurements: [[Double]]) { + self.table_name = table_name + self.column_names = column_names + self.replace_measurements = replace_measurements + } + + /// Creates a table update for code coverage by parsing a coverage report from XCov. + public static func createFrom(coverage: CoverageReport, pullRequest: Int) -> TableUpdate { + var metrics = [[Double]]() + for target in coverage.targets { + let sdkKey = TARGET_TO_SDK_INDEX[target.name] + if sdkKey == nil { + print("WARNING - target \(target.name) has no mapping to an SDK id. Skipping...") + } else { + var row = [Double]() + row.append(Double(pullRequest)) + row.append(sdkKey!) + row.append(target.coverage) + metrics.append(row) + } + } + let columnNames = ["pull_request_id", "sdk_id", "coverage_percent"] + return TableUpdate(table_name: "Coverage1", column_names: columnNames, replace_measurements: metrics) + } +} diff --git a/Metrics/Tests/MetricsTests/CoverageReportTests.swift b/Metrics/Tests/MetricsTests/CoverageReportTests.swift new file mode 100644 index 00000000000..87bbe299fa1 --- /dev/null +++ b/Metrics/Tests/MetricsTests/CoverageReportTests.swift @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MetricsLib +import XCTest + +let EXAMPLE_REPORT = "Tests/MetricsTests/example_report.json" + +final class CoverageReportTests: XCTestCase { + func testShouldParseTotalCoverage() throws { + let report = try CoverageReport.load(path: EXAMPLE_REPORT) + XCTAssertEqual(report.coverage, 0.5490569575543673) + } + + func testShouldParseTargets() throws { + let report = try CoverageReport.load(path: EXAMPLE_REPORT) + XCTAssertEqual(report.targets.count, 14) + XCTAssertEqual(report.targets[0].name, "Auth_Example_iOS.app") + XCTAssertEqual(report.targets[0].coverage, 0.8241201927002532) + XCTAssertEqual(report.targets[0].files.count, 97) + } + + func testShouldFailOnMissingFile() throws { + XCTAssertThrowsError(try CoverageReport.load(path: "IDontExist")) + } +} diff --git a/Metrics/Tests/MetricsTests/UploadMetricsTests.swift b/Metrics/Tests/MetricsTests/UploadMetricsTests.swift new file mode 100644 index 00000000000..2491417db37 --- /dev/null +++ b/Metrics/Tests/MetricsTests/UploadMetricsTests.swift @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MetricsLib +import XCTest + +let PULL_REQUEST = 777 + +final class UploadMetricsTests: XCTestCase { + func testShouldCreateTableUpdateFromCoverageReport() { + let target_one = Target(name: "Auth_Example_iOS.app", coverage: 0.1) + let target_two = Target(name: "Core_Example_iOS.app", coverage: 0.2) + let report = CoverageReport(targets: [target_one, target_two], coverage: 0.15) + let metricsUpdate = TableUpdate.createFrom(coverage: report, pullRequest: PULL_REQUEST) + XCTAssertEqual(metricsUpdate.table_name, "Coverage1") + XCTAssertEqual(metricsUpdate.replace_measurements.count, 2) + XCTAssertEqual(metricsUpdate.replace_measurements[0], + [Double(PULL_REQUEST), 0, target_one.coverage]) + XCTAssertEqual(metricsUpdate.replace_measurements[1], + [Double(PULL_REQUEST), 1, target_two.coverage]) + } + + func testShouldIgnoreUnkownTargets() { + let target = Target(name: "Unknown_Target", coverage: 0.3) + let report = CoverageReport(targets: [target], coverage: 0.15) + let metrics = TableUpdate.createFrom(coverage: report, pullRequest: PULL_REQUEST) + XCTAssertEqual(metrics.table_name, "Coverage1") + XCTAssertEqual(metrics.replace_measurements.count, 0) + } + + func testShouldConvertToJson() throws { + let table = TableUpdate(table_name: "name", + column_names: ["col"], + replace_measurements: [[0], [2]]) + let metrics = UploadMetrics(tables: [table]) + let json = try metrics.json() + XCTAssertEqual(json, "{\"tables\":[{\"replace_measurements\":[[0],[2]],\"column_names\":[\"col\"],\"table_name\":\"name\"}]}") + } +} diff --git a/Metrics/Tests/MetricsTests/example_report.json b/Metrics/Tests/MetricsTests/example_report.json new file mode 100644 index 00000000000..08f775a87d6 --- /dev/null +++ b/Metrics/Tests/MetricsTests/example_report.json @@ -0,0 +1,41997 @@ +{ + "coverage": 0.5490569575543673, + "targets": [ + { + "name": "Auth_Example_iOS.app", + "coverage": 0.8241201927002532, + "files": [ + { + "name": "FIRGameCenterAuthCredential.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FIRGameCenterAuthCredential initWithProvider:]", + "coverage": 0 + }, + { + "name": "-[FIRGameCenterAuthCredential initWithPlayerID:publicKeyURL:signature:salt:timestamp:displayName:]", + "coverage": 0 + }, + { + "name": "-[FIRGameCenterAuthCredential prepareVerifyAssertionRequest:]", + "coverage": 0 + }, + { + "name": "+[FIRGameCenterAuthCredential supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRGameCenterAuthCredential initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRGameCenterAuthCredential encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthExceptionUtils.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason:]", + "coverage": 0 + }, + { + "name": "+[FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthWebView.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthWebView initWithFrame:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebView initializeSubviews]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebView layoutSubviews]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebView createWebView]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebView createSpinner]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthWebViewController.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthWebViewController initWithURL:delegate:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebViewController loadView]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebViewController viewDidAppear:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebViewController cancel]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebViewController webView:shouldStartLoadWithRequest:navigationType:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebViewController webViewDidStartLoad:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebViewController webViewDidFinishLoad:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthWebViewController webView:didFailLoadWithError:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRGameCenterAuthProvider.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FIRGameCenterAuthProvider init]", + "coverage": 0 + }, + { + "name": "+[FIRGameCenterAuthProvider getCredentialWithCompletion:]", + "coverage": 0 + }, + { + "name": "__57+[FIRGameCenterAuthProvider getCredentialWithCompletion:]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "FIRSecureTokenResponse.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FIRSecureTokenResponse expectedKind]", + "coverage": 0 + }, + { + "name": "-[FIRSecureTokenResponse setWithDictionary:error:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthDefaultUIDelegate.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthDefaultUIDelegate initWithViewController:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthDefaultUIDelegate presentViewController:animated:completion:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthDefaultUIDelegate dismissViewControllerAnimated:completion:]", + "coverage": 0 + }, + { + "name": "+[FIRAuthDefaultUIDelegate defaultUIDelegate]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthTokenResult.m", + "coverage": 0.2727272727272727, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthTokenResult initWithToken:expirationDate:authDate:issuedAtDate:signInProvider:claims:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthTokenResult supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRAuthTokenResult initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthTokenResult encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRSecureTokenRequest.m", + "coverage": 0.27941176470588236, + "type": "objc", + "functions": [ + { + "name": "+[FIRSecureTokenRequest authCodeRequestWithCode:requestConfiguration:]", + "coverage": 0 + }, + { + "name": "+[FIRSecureTokenRequest refreshRequestWithRefreshToken:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "+[FIRSecureTokenRequest grantTypeStringWithGrantType:]", + "coverage": 0 + }, + { + "name": "-[FIRSecureTokenRequest initWithGrantType:scope:refreshToken:code:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRSecureTokenRequest requestConfiguration]", + "coverage": 0 + }, + { + "name": "-[FIRSecureTokenRequest requestURL]", + "coverage": 0 + }, + { + "name": "-[FIRSecureTokenRequest containsPostBody]", + "coverage": 0 + }, + { + "name": "-[FIRSecureTokenRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 0 + }, + { + "name": "+[FIRSecureTokenRequest host]", + "coverage": 0 + }, + { + "name": "+[FIRSecureTokenRequest setHost:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAppDelegate.m", + "coverage": 0.3333333333333333, + "type": "objc", + "functions": [ + { + "name": "-[FIRAppDelegate application:didFinishLaunchingWithOptions:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillResignActive:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidEnterBackground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationWillEnterForeground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidBecomeActive:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillTerminate:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthDataResult.m", + "coverage": 0.3333333333333333, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthDataResult initWithUser:additionalUserInfo:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthDataResult supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRAuthDataResult initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthDataResult encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIREmailPasswordAuthCredential.m", + "coverage": 0.34782608695652173, + "type": "objc", + "functions": [ + { + "name": "-[FIREmailPasswordAuthCredential initWithProvider:]", + "coverage": 0 + }, + { + "name": "-[FIREmailPasswordAuthCredential initWithEmail:password:]", + "coverage": 1 + }, + { + "name": "-[FIREmailPasswordAuthCredential initWithEmail:link:]", + "coverage": 1 + }, + { + "name": "-[FIREmailPasswordAuthCredential prepareVerifyAssertionRequest:]", + "coverage": 0 + }, + { + "name": "+[FIREmailPasswordAuthCredential supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIREmailPasswordAuthCredential initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIREmailPasswordAuthCredential encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRPhoneAuthCredential.m", + "coverage": 0.36363636363636365, + "type": "objc", + "functions": [ + { + "name": "-[FIRPhoneAuthCredential initWithTemporaryProof:phoneNumber:providerID:]", + "coverage": 1 + }, + { + "name": "-[FIRPhoneAuthCredential initWithProvider:]", + "coverage": 0 + }, + { + "name": "-[FIRPhoneAuthCredential initWithProviderID:verificationID:verificationCode:]", + "coverage": 1 + }, + { + "name": "+[FIRPhoneAuthCredential supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRPhoneAuthCredential initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRPhoneAuthCredential encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRTwitterAuthProvider.m", + "coverage": 0.375, + "type": "objc", + "functions": [ + { + "name": "-[FIRTwitterAuthProvider init]", + "coverage": 0 + }, + { + "name": "+[FIRTwitterAuthProvider credentialWithToken:secret:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRFacebookAuthProvider.m", + "coverage": 0.375, + "type": "objc", + "functions": [ + { + "name": "-[FIRFacebookAuthProvider init]", + "coverage": 0 + }, + { + "name": "+[FIRFacebookAuthProvider credentialWithAccessToken:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRGitHubAuthProvider.m", + "coverage": 0.375, + "type": "objc", + "functions": [ + { + "name": "-[FIRGitHubAuthProvider init]", + "coverage": 0 + }, + { + "name": "+[FIRGitHubAuthProvider credentialWithToken:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRGoogleAuthProvider.m", + "coverage": 0.375, + "type": "objc", + "functions": [ + { + "name": "-[FIRGoogleAuthProvider init]", + "coverage": 0 + }, + { + "name": "+[FIRGoogleAuthProvider credentialWithIDToken:accessToken:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRGitHubAuthCredential.m", + "coverage": 0.38461538461538464, + "type": "objc", + "functions": [ + { + "name": "-[FIRGitHubAuthCredential initWithProvider:]", + "coverage": 0 + }, + { + "name": "-[FIRGitHubAuthCredential initWithToken:]", + "coverage": 1 + }, + { + "name": "-[FIRGitHubAuthCredential prepareVerifyAssertionRequest:]", + "coverage": 1 + }, + { + "name": "+[FIRGitHubAuthCredential supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRGitHubAuthCredential initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRGitHubAuthCredential encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRFacebookAuthCredential.m", + "coverage": 0.38461538461538464, + "type": "objc", + "functions": [ + { + "name": "-[FIRFacebookAuthCredential initWithProvider:]", + "coverage": 0 + }, + { + "name": "-[FIRFacebookAuthCredential initWithAccessToken:]", + "coverage": 1 + }, + { + "name": "-[FIRFacebookAuthCredential prepareVerifyAssertionRequest:]", + "coverage": 1 + }, + { + "name": "+[FIRFacebookAuthCredential supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRFacebookAuthCredential initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRFacebookAuthCredential encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthCredential.m", + "coverage": 0.3888888888888889, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthCredential init]", + "coverage": 0 + }, + { + "name": "-[FIRAuthCredential initWithProvider:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthCredential prepareVerifyAssertionRequest:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRTwitterAuthCredential.m", + "coverage": 0.4, + "type": "objc", + "functions": [ + { + "name": "-[FIRTwitterAuthCredential initWithProvider:]", + "coverage": 0 + }, + { + "name": "-[FIRTwitterAuthCredential initWithToken:secret:]", + "coverage": 1 + }, + { + "name": "-[FIRTwitterAuthCredential prepareVerifyAssertionRequest:]", + "coverage": 1 + }, + { + "name": "+[FIRTwitterAuthCredential supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRTwitterAuthCredential initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRTwitterAuthCredential encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRGoogleAuthCredential.m", + "coverage": 0.4, + "type": "objc", + "functions": [ + { + "name": "-[FIRGoogleAuthCredential initWithProvider:]", + "coverage": 0 + }, + { + "name": "-[FIRGoogleAuthCredential initWithIDToken:accessToken:]", + "coverage": 1 + }, + { + "name": "-[FIRGoogleAuthCredential prepareVerifyAssertionRequest:]", + "coverage": 1 + }, + { + "name": "+[FIRGoogleAuthCredential supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRGoogleAuthCredential initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRGoogleAuthCredential encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIROAuthCredential.m", + "coverage": 0.4772727272727273, + "type": "objc", + "functions": [ + { + "name": "-[FIROAuthCredential initWithProvider:]", + "coverage": 0 + }, + { + "name": "-[FIROAuthCredential initWithProviderID:IDToken:accessToken:pendingToken:]", + "coverage": 1 + }, + { + "name": "-[FIROAuthCredential initWithProviderID:sessionID:OAuthResponseURLString:]", + "coverage": 1 + }, + { + "name": "-[FIROAuthCredential prepareVerifyAssertionRequest:]", + "coverage": 1 + }, + { + "name": "+[FIROAuthCredential supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIROAuthCredential initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIROAuthCredential encodeWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthURLPresenter.m", + "coverage": 0.496551724137931, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthURLPresenter presentURL:UIDelegate:callbackMatcher:completion:]", + "coverage": 0.8461538461538461 + }, + { + "name": "__72-[FIRAuthURLPresenter presentURL:UIDelegate:callbackMatcher:completion:]_block_invoke", + "coverage": 0.6875 + }, + { + "name": "-[FIRAuthURLPresenter canHandleURL:]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FIRAuthURLPresenter safariViewControllerDidFinish:]", + "coverage": 0 + }, + { + "name": "__53-[FIRAuthURLPresenter safariViewControllerDidFinish:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuthURLPresenter webViewController:canHandleURL:]", + "coverage": 0 + }, + { + "name": "__54-[FIRAuthURLPresenter webViewController:canHandleURL:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuthURLPresenter webViewControllerDidCancel:]", + "coverage": 0 + }, + { + "name": "__50-[FIRAuthURLPresenter webViewControllerDidCancel:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuthURLPresenter webViewController:didFailWithError:]", + "coverage": 0 + }, + { + "name": "__58-[FIRAuthURLPresenter webViewController:didFailWithError:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuthURLPresenter finishPresentationWithURL:error:]", + "coverage": 0.9166666666666666 + }, + { + "name": "__55-[FIRAuthURLPresenter finishPresentationWithURL:error:]_block_invoke", + "coverage": 1 + }, + { + "name": "__55-[FIRAuthURLPresenter finishPresentationWithURL:error:]_block_invoke.42", + "coverage": 1 + }, + { + "name": "__55-[FIRAuthURLPresenter finishPresentationWithURL:error:]_block_invoke_2", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthAPNSTokenManager.m", + "coverage": 0.49743589743589745, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthAPNSTokenManager initWithApplication:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAPNSTokenManager getTokenWithCallback:]", + "coverage": 1 + }, + { + "name": "__48-[FIRAuthAPNSTokenManager getTokenWithCallback:]_block_invoke", + "coverage": 1 + }, + { + "name": "__48-[FIRAuthAPNSTokenManager getTokenWithCallback:]_block_invoke.13", + "coverage": 1 + }, + { + "name": "-[FIRAuthAPNSTokenManager setToken:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAPNSTokenManager cancelWithError:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAPNSTokenManager callBackWithToken:error:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthAPNSTokenManager isProductionApp]", + "coverage": 0.08411214953271028 + } + ] + }, + { + "name": "FIRViewController.m", + "coverage": 0.5, + "type": "objc", + "functions": [ + { + "name": "-[FIRViewController viewDidLoad]", + "coverage": 1 + }, + { + "name": "-[FIRViewController didReceiveMemoryWarning]", + "coverage": 0 + } + ] + }, + { + "name": "FIREmailAuthProvider.m", + "coverage": 0.5454545454545454, + "type": "objc", + "functions": [ + { + "name": "-[FIREmailAuthProvider init]", + "coverage": 0 + }, + { + "name": "+[FIREmailAuthProvider credentialWithEmail:password:]", + "coverage": 1 + }, + { + "name": "+[FIREmailAuthProvider credentialWithEmail:link:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRSendVerificationCodeResponse.m", + "coverage": 0.5714285714285714, + "type": "objc", + "functions": [ + { + "name": "-[FIRSendVerificationCodeResponse expectedKind]", + "coverage": 0 + }, + { + "name": "-[FIRSendVerificationCodeResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRCreateAuthURIRequest.m", + "coverage": 0.6666666666666666, + "type": "objc", + "functions": [ + { + "name": "-[FIRCreateAuthURIRequest initWithIdentifier:continueURI:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRCreateAuthURIRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 0.5454545454545454 + } + ] + }, + { + "name": "FIRGetProjectConfigResponse.m", + "coverage": 0.6666666666666666, + "type": "objc", + "functions": [ + { + "name": "-[FIRGetProjectConfigResponse setWithDictionary:error:]", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "FIRAuthKeychain.m", + "coverage": 0.7413793103448276, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthKeychain initWithService:]", + "coverage": 0.9090909090909091 + }, + { + "name": "-[FIRAuthKeychain dataForKey:error:]", + "coverage": 0.6363636363636364 + }, + { + "name": "-[FIRAuthKeychain setData:forKey:error:]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FIRAuthKeychain removeDataForKey:error:]", + "coverage": 0.5714285714285714 + }, + { + "name": "-[FIRAuthKeychain itemWithQuery:error:]", + "coverage": 0.75 + }, + { + "name": "-[FIRAuthKeychain setItemWithQuery:attributes:error:]", + "coverage": 0.7 + }, + { + "name": "-[FIRAuthKeychain deleteItemWithQuery:error:]", + "coverage": 0.5 + }, + { + "name": "-[FIRAuthKeychain deleteLegacyItemWithKey:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthKeychain genericPasswordQueryWithKey:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthKeychain legacyGenericPasswordQueryWithKey:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRIdentityToolkitRequest.m", + "coverage": 0.7692307692307693, + "type": "objc", + "functions": [ + { + "name": "-[FIRIdentityToolkitRequest initWithEndpoint:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRIdentityToolkitRequest containsPostBody]", + "coverage": 1 + }, + { + "name": "-[FIRIdentityToolkitRequest requestURL]", + "coverage": 1 + }, + { + "name": "-[FIRIdentityToolkitRequest requestConfiguration]", + "coverage": 1 + }, + { + "name": "+[FIRIdentityToolkitRequest host]", + "coverage": 0 + }, + { + "name": "+[FIRIdentityToolkitRequest setHost:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRVerifyPhoneNumberResponse.m", + "coverage": 0.7857142857142857, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyPhoneNumberResponse expectedKind]", + "coverage": 0 + }, + { + "name": "-[FIRVerifyPhoneNumberResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRDependency.m", + "coverage": 0.7857142857142857, + "type": "objc", + "functions": [ + { + "name": "+[FIRDependency dependencyWithProtocol:]", + "coverage": 0 + }, + { + "name": "+[FIRDependency dependencyWithProtocol:isRequired:]", + "coverage": 1 + }, + { + "name": "-[FIRDependency initWithProtocol:isRequired:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRBundleUtil.m", + "coverage": 0.7872340425531915, + "type": "objc", + "functions": [ + { + "name": "+[FIRBundleUtil relevantBundles]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil optionsDictionaryPathWithResourceName:andFileType:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil relevantURLSchemes]", + "coverage": 0 + }, + { + "name": "+[FIRBundleUtil hasBundleIdentifierPrefix:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil bundleIdentifierByRemovingLastPartFrom:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRUser.m", + "coverage": 0.7953100158982512, + "type": "objc", + "functions": [ + { + "name": "callInMainThreadWithError", + "coverage": 1 + }, + { + "name": "__callInMainThreadWithError_block_invoke", + "coverage": 1 + }, + { + "name": "callInMainThreadWithUserAndError", + "coverage": 1 + }, + { + "name": "__callInMainThreadWithUserAndError_block_invoke", + "coverage": 1 + }, + { + "name": "callInMainThreadWithAuthDataResultAndError", + "coverage": 1 + }, + { + "name": "__callInMainThreadWithAuthDataResultAndError_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRUser retrieveUserWithAuth:accessToken:accessTokenExpirationDate:refreshToken:anonymous:callback:]", + "coverage": 1 + }, + { + "name": "__102+[FIRUser retrieveUserWithAuth:accessToken:accessTokenExpirationDate:refreshToken:anonymous:callback:]_block_invoke", + "coverage": 0.8571428571428571 + }, + { + "name": "__102+[FIRUser retrieveUserWithAuth:accessToken:accessTokenExpirationDate:refreshToken:anonymous:callback:]_block_invoke_2", + "coverage": 0.6 + }, + { + "name": "-[FIRUser initWithTokenService:]", + "coverage": 1 + }, + { + "name": "+[FIRUser supportsSecureCoding]", + "coverage": 1 + }, + { + "name": "-[FIRUser initWithCoder:]", + "coverage": 0.96 + }, + { + "name": "-[FIRUser encodeWithCoder:]", + "coverage": 1 + }, + { + "name": "-[FIRUser setAuth:]", + "coverage": 1 + }, + { + "name": "-[FIRUser providerID]", + "coverage": 1 + }, + { + "name": "-[FIRUser providerData]", + "coverage": 1 + }, + { + "name": "-[FIRUser getAccountInfoRefreshingCache:]", + "coverage": 1 + }, + { + "name": "__41-[FIRUser getAccountInfoRefreshingCache:]_block_invoke", + "coverage": 0.875 + }, + { + "name": "__41-[FIRUser getAccountInfoRefreshingCache:]_block_invoke_2", + "coverage": 0.7692307692307693 + }, + { + "name": "-[FIRUser updateWithGetAccountInfoResponse:]", + "coverage": 1 + }, + { + "name": "-[FIRUser executeUserUpdateWithChanges:callback:]", + "coverage": 1 + }, + { + "name": "__49-[FIRUser executeUserUpdateWithChanges:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "__49-[FIRUser executeUserUpdateWithChanges:callback:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__49-[FIRUser executeUserUpdateWithChanges:callback:]_block_invoke_3", + "coverage": 0.8947368421052632 + }, + { + "name": "__49-[FIRUser executeUserUpdateWithChanges:callback:]_block_invoke_4", + "coverage": 0.5 + }, + { + "name": "__49-[FIRUser executeUserUpdateWithChanges:callback:]_block_invoke_5", + "coverage": 0 + }, + { + "name": "-[FIRUser updateKeychain:]", + "coverage": 1 + }, + { + "name": "-[FIRUser setTokenService:callback:]", + "coverage": 1 + }, + { + "name": "__36-[FIRUser setTokenService:callback:]_block_invoke", + "coverage": 0.5 + }, + { + "name": "-[FIRUser updateEmail:password:callback:]", + "coverage": 1 + }, + { + "name": "__41-[FIRUser updateEmail:password:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "__41-[FIRUser updateEmail:password:callback:]_block_invoke.205", + "coverage": 0.95 + }, + { + "name": "__41-[FIRUser updateEmail:password:callback:]_block_invoke_2", + "coverage": 0.9230769230769231 + }, + { + "name": "__41-[FIRUser updateEmail:password:callback:]_block_invoke_3", + "coverage": 0.7407407407407407 + }, + { + "name": "-[FIRUser updateEmail:completion:]", + "coverage": 1 + }, + { + "name": "__34-[FIRUser updateEmail:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__34-[FIRUser updateEmail:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRUser updatePassword:completion:]", + "coverage": 1 + }, + { + "name": "__37-[FIRUser updatePassword:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__37-[FIRUser updatePassword:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRUser internalUpdateOrLinkPhoneNumberCredential:isLinkOperation:completion:]", + "coverage": 1 + }, + { + "name": "__80-[FIRUser internalUpdateOrLinkPhoneNumberCredential:isLinkOperation:completion:]_block_invoke", + "coverage": 0.9210526315789473 + }, + { + "name": "__80-[FIRUser internalUpdateOrLinkPhoneNumberCredential:isLinkOperation:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__80-[FIRUser internalUpdateOrLinkPhoneNumberCredential:isLinkOperation:completion:]_block_invoke_3", + "coverage": 0.46153846153846156 + }, + { + "name": "-[FIRUser updatePhoneNumberCredential:completion:]", + "coverage": 1 + }, + { + "name": "__50-[FIRUser updatePhoneNumberCredential:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__50-[FIRUser updatePhoneNumberCredential:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRUser profileChangeRequest]", + "coverage": 1 + }, + { + "name": "__31-[FIRUser profileChangeRequest]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRUser setDisplayName:]", + "coverage": 1 + }, + { + "name": "-[FIRUser setPhotoURL:]", + "coverage": 1 + }, + { + "name": "-[FIRUser rawAccessToken]", + "coverage": 1 + }, + { + "name": "-[FIRUser accessTokenExpirationDate]", + "coverage": 1 + }, + { + "name": "-[FIRUser reloadWithCompletion:]", + "coverage": 1 + }, + { + "name": "__32-[FIRUser reloadWithCompletion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__32-[FIRUser reloadWithCompletion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRUser reauthenticateWithCredential:completion:]", + "coverage": 1 + }, + { + "name": "__51-[FIRUser reauthenticateWithCredential:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRUser reauthenticateAndRetrieveDataWithCredential:completion:]", + "coverage": 1 + }, + { + "name": "__66-[FIRUser reauthenticateAndRetrieveDataWithCredential:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__66-[FIRUser reauthenticateAndRetrieveDataWithCredential:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__66-[FIRUser reauthenticateAndRetrieveDataWithCredential:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRUser refreshToken]", + "coverage": 1 + }, + { + "name": "__23-[FIRUser refreshToken]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRUser getIDTokenWithCompletion:]", + "coverage": 0 + }, + { + "name": "-[FIRUser getIDTokenForcingRefresh:completion:]", + "coverage": 0 + }, + { + "name": "__47-[FIRUser getIDTokenForcingRefresh:completion:]_block_invoke", + "coverage": 0 + }, + { + "name": "__47-[FIRUser getIDTokenForcingRefresh:completion:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRUser getIDTokenResultWithCompletion:]", + "coverage": 1 + }, + { + "name": "__42-[FIRUser getIDTokenResultWithCompletion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__42-[FIRUser getIDTokenResultWithCompletion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRUser getIDTokenResultForcingRefresh:completion:]", + "coverage": 1 + }, + { + "name": "__53-[FIRUser getIDTokenResultForcingRefresh:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__53-[FIRUser getIDTokenResultForcingRefresh:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__53-[FIRUser getIDTokenResultForcingRefresh:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRUser parseIDToken:error:]", + "coverage": 0.75 + }, + { + "name": "-[FIRUser internalGetTokenWithCallback:]", + "coverage": 1 + }, + { + "name": "-[FIRUser internalGetTokenForcingRefresh:callback:]", + "coverage": 1 + }, + { + "name": "__51-[FIRUser internalGetTokenForcingRefresh:callback:]_block_invoke", + "coverage": 0.7857142857142857 + }, + { + "name": "-[FIRUser linkWithCredential:completion:]", + "coverage": 1 + }, + { + "name": "__41-[FIRUser linkWithCredential:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRUser linkAndRetrieveDataWithCredential:completion:]", + "coverage": 1 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke", + "coverage": 0.6848484848484848 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke.415", + "coverage": 0 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke_2.431", + "coverage": 0 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke_3", + "coverage": 0 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke_4", + "coverage": 0 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke.448", + "coverage": 1 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke.452", + "coverage": 1 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke_2.453", + "coverage": 1 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke.457", + "coverage": 0.9464285714285714 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke_2.463", + "coverage": 1 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke_3.467", + "coverage": 0.88 + }, + { + "name": "__56-[FIRUser linkAndRetrieveDataWithCredential:completion:]_block_invoke_4.468", + "coverage": 0.5 + }, + { + "name": "-[FIRUser unlinkFromProvider:completion:]", + "coverage": 1 + }, + { + "name": "__41-[FIRUser unlinkFromProvider:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__41-[FIRUser unlinkFromProvider:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__41-[FIRUser unlinkFromProvider:completion:]_block_invoke.493", + "coverage": 0.8382352941176471 + }, + { + "name": "__41-[FIRUser unlinkFromProvider:completion:]_block_invoke_2.500", + "coverage": 0.5365853658536586 + }, + { + "name": "__41-[FIRUser unlinkFromProvider:completion:]_block_invoke_3", + "coverage": 0 + }, + { + "name": "-[FIRUser sendEmailVerificationWithCompletion:]", + "coverage": 0 + }, + { + "name": "-[FIRUser sendEmailVerificationWithActionCodeSettings:completion:]", + "coverage": 0 + }, + { + "name": "-[FIRUser sendEmailVerificationWithNullableActionCodeSettings:completion:]", + "coverage": 0 + }, + { + "name": "__74-[FIRUser sendEmailVerificationWithNullableActionCodeSettings:completion:]_block_invoke", + "coverage": 0 + }, + { + "name": "__74-[FIRUser sendEmailVerificationWithNullableActionCodeSettings:completion:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__74-[FIRUser sendEmailVerificationWithNullableActionCodeSettings:completion:]_block_invoke_3", + "coverage": 0 + }, + { + "name": "-[FIRUser deleteWithCompletion:]", + "coverage": 0 + }, + { + "name": "__32-[FIRUser deleteWithCompletion:]_block_invoke", + "coverage": 0 + }, + { + "name": "__32-[FIRUser deleteWithCompletion:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__32-[FIRUser deleteWithCompletion:]_block_invoke_3", + "coverage": 0 + }, + { + "name": "-[FIRUser signOutIfTokenIsInvalidWithError:]", + "coverage": 1 + }, + { + "name": "-[FIRUserProfileChangeRequest initWithUser:]", + "coverage": 1 + }, + { + "name": "-[FIRUserProfileChangeRequest displayName]", + "coverage": 0 + }, + { + "name": "-[FIRUserProfileChangeRequest setDisplayName:]", + "coverage": 1 + }, + { + "name": "__46-[FIRUserProfileChangeRequest setDisplayName:]_block_invoke", + "coverage": 0.5 + }, + { + "name": "-[FIRUserProfileChangeRequest photoURL]", + "coverage": 0 + }, + { + "name": "-[FIRUserProfileChangeRequest setPhotoURL:]", + "coverage": 1 + }, + { + "name": "__43-[FIRUserProfileChangeRequest setPhotoURL:]_block_invoke", + "coverage": 0.5 + }, + { + "name": "-[FIRUserProfileChangeRequest hasUpdates]", + "coverage": 1 + }, + { + "name": "-[FIRUserProfileChangeRequest commitChangesWithCompletion:]", + "coverage": 1 + }, + { + "name": "__59-[FIRUserProfileChangeRequest commitChangesWithCompletion:]_block_invoke", + "coverage": 0.8181818181818182 + }, + { + "name": "__59-[FIRUserProfileChangeRequest commitChangesWithCompletion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__59-[FIRUserProfileChangeRequest commitChangesWithCompletion:]_block_invoke.711", + "coverage": 0.8235294117647058 + } + ] + }, + { + "name": "FIRConfiguration.m", + "coverage": 0.8076923076923077, + "type": "objc", + "functions": [ + { + "name": "+[FIRConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__34+[FIRConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration init]", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration setLoggerLevel:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRLogger.m", + "coverage": 0.8118811881188119, + "type": "objc", + "functions": [ + { + "name": "FIRLoggerInitializeASL", + "coverage": 1 + }, + { + "name": "__FIRLoggerInitializeASL_block_invoke", + "coverage": 0.9333333333333333 + }, + { + "name": "FIRSetAnalyticsDebugMode", + "coverage": 0 + }, + { + "name": "FIRSetLoggerLevel", + "coverage": 0 + }, + { + "name": "FIRResetLogger", + "coverage": 1 + }, + { + "name": "FIRSetLoggerUserDefaults", + "coverage": 1 + }, + { + "name": "FIRIsLoggableLevel", + "coverage": 0 + }, + { + "name": "FIRLogBasic", + "coverage": 1 + }, + { + "name": "Definition at 158:95", + "coverage": 1 + }, + { + "name": "+[FIRLoggerWrapper logWithLevel:withService:withCode:withMessage:withArgs:]", + "coverage": 0 + } + ] + }, + { + "name": "FIROAuthProvider.m", + "coverage": 0.8430379746835444, + "type": "objc", + "functions": [ + { + "name": "+[FIROAuthProvider credentialWithProviderID:IDToken:accessToken:pendingToken:]", + "coverage": 0 + }, + { + "name": "+[FIROAuthProvider credentialWithProviderID:IDToken:accessToken:]", + "coverage": 1 + }, + { + "name": "+[FIROAuthProvider credentialWithProviderID:accessToken:]", + "coverage": 1 + }, + { + "name": "+[FIROAuthProvider providerWithProviderID:]", + "coverage": 0 + }, + { + "name": "+[FIROAuthProvider providerWithProviderID:auth:]", + "coverage": 1 + }, + { + "name": "-[FIROAuthProvider getCredentialWithUIDelegate:completion:]", + "coverage": 0.9344262295081968 + }, + { + "name": "__59-[FIROAuthProvider getCredentialWithUIDelegate:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__59-[FIROAuthProvider getCredentialWithUIDelegate:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__59-[FIROAuthProvider getCredentialWithUIDelegate:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "__59-[FIROAuthProvider getCredentialWithUIDelegate:completion:]_block_invoke.25", + "coverage": 0.9142857142857143 + }, + { + "name": "__59-[FIROAuthProvider getCredentialWithUIDelegate:completion:]_block_invoke_2.26", + "coverage": 1 + }, + { + "name": "__59-[FIROAuthProvider getCredentialWithUIDelegate:completion:]_block_invoke.35", + "coverage": 1 + }, + { + "name": "-[FIROAuthProvider initWithProviderID:auth:]", + "coverage": 1 + }, + { + "name": "-[FIROAuthProvider OAuthResponseForURL:error:]", + "coverage": 0.8484848484848485 + }, + { + "name": "-[FIROAuthProvider getHeadFulLiteURLWithEventID:sessionID:completion:]", + "coverage": 1 + }, + { + "name": "__70-[FIROAuthProvider getHeadFulLiteURLWithEventID:sessionID:completion:]_block_invoke", + "coverage": 0.6086956521739131 + }, + { + "name": "-[FIROAuthProvider customParametersStringWithError:]", + "coverage": 0 + }, + { + "name": "-[FIROAuthProvider hashforString:]", + "coverage": 0.8888888888888888 + }, + { + "name": "-[FIROAuthProvider hexStringFromData:]", + "coverage": 1 + }, + { + "name": "-[FIROAuthProvider httpArgumentsStringForArgsDictionary:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuth.m", + "coverage": 0.8576283987915407, + "type": "objc", + "functions": [ + { + "name": "-[FIRActionCodeInfo dataForKey:]", + "coverage": 0.875 + }, + { + "name": "-[FIRActionCodeInfo initWithOperation:email:newEmail:]", + "coverage": 1 + }, + { + "name": "+[FIRActionCodeInfo actionCodeOperationForRequestType:]", + "coverage": 0.4666666666666667 + }, + { + "name": "+[FIRAuth load]", + "coverage": 1 + }, + { + "name": "+[FIRAuth initialize]", + "coverage": 1 + }, + { + "name": "+[FIRAuth auth]", + "coverage": 1 + }, + { + "name": "+[FIRAuth authWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth initWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth initWithAPIKey:appName:]", + "coverage": 1 + }, + { + "name": "__34-[FIRAuth initWithAPIKey:appName:]_block_invoke", + "coverage": 0.8611111111111112 + }, + { + "name": "-[FIRAuth dealloc]", + "coverage": 0.7894736842105263 + }, + { + "name": "-[FIRAuth currentUser]", + "coverage": 1 + }, + { + "name": "__22-[FIRAuth currentUser]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth fetchProvidersForEmail:completion:]", + "coverage": 1 + }, + { + "name": "__45-[FIRAuth fetchProvidersForEmail:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__45-[FIRAuth fetchProvidersForEmail:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__45-[FIRAuth fetchProvidersForEmail:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInWithProvider:UIDelegate:completion:]", + "coverage": 1 + }, + { + "name": "__52-[FIRAuth signInWithProvider:UIDelegate:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__52-[FIRAuth signInWithProvider:UIDelegate:completion:]_block_invoke_2", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRAuth fetchSignInMethodsForEmail:completion:]", + "coverage": 1 + }, + { + "name": "__49-[FIRAuth fetchSignInMethodsForEmail:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__49-[FIRAuth fetchSignInMethodsForEmail:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__49-[FIRAuth fetchSignInMethodsForEmail:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInWithEmail:password:completion:]", + "coverage": 1 + }, + { + "name": "__47-[FIRAuth signInWithEmail:password:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__47-[FIRAuth signInWithEmail:password:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInWithEmail:link:completion:]", + "coverage": 1 + }, + { + "name": "__43-[FIRAuth signInWithEmail:link:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__43-[FIRAuth signInWithEmail:link:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInWithEmail:password:callback:]", + "coverage": 1 + }, + { + "name": "__45-[FIRAuth signInWithEmail:password:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInAndRetrieveDataWithEmail:password:completion:]", + "coverage": 1 + }, + { + "name": "__62-[FIRAuth signInAndRetrieveDataWithEmail:password:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth internalSignInAndRetrieveDataWithEmail:password:completion:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInAndRetrieveDataWithGameCenterCredential:callback:]", + "coverage": 0 + }, + { + "name": "__66-[FIRAuth signInAndRetrieveDataWithGameCenterCredential:callback:]_block_invoke", + "coverage": 0 + }, + { + "name": "__66-[FIRAuth signInAndRetrieveDataWithGameCenterCredential:callback:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRAuth internalSignInAndRetrieveDataWithEmail:link:callback:]", + "coverage": 0.8653846153846154 + }, + { + "name": "__64-[FIRAuth internalSignInAndRetrieveDataWithEmail:link:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "__64-[FIRAuth internalSignInAndRetrieveDataWithEmail:link:callback:]_block_invoke_2", + "coverage": 0.7368421052631579 + }, + { + "name": "-[FIRAuth signInWithCredential:completion:]", + "coverage": 1 + }, + { + "name": "__43-[FIRAuth signInWithCredential:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInAndRetrieveDataWithCredential:completion:]", + "coverage": 1 + }, + { + "name": "__58-[FIRAuth signInAndRetrieveDataWithCredential:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth internalSignInWithCredential:callback:]", + "coverage": 1 + }, + { + "name": "__49-[FIRAuth internalSignInWithCredential:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth internalSignInAndRetrieveDataWithCredential:isReauthentication:callback:]", + "coverage": 0.9615384615384616 + }, + { + "name": "__83-[FIRAuth internalSignInAndRetrieveDataWithCredential:isReauthentication:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "__83-[FIRAuth internalSignInAndRetrieveDataWithCredential:isReauthentication:callback:]_block_invoke.333", + "coverage": 1 + }, + { + "name": "__83-[FIRAuth internalSignInAndRetrieveDataWithCredential:isReauthentication:callback:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__83-[FIRAuth internalSignInAndRetrieveDataWithCredential:isReauthentication:callback:]_block_invoke.365", + "coverage": 0.8648648648648649 + }, + { + "name": "__83-[FIRAuth internalSignInAndRetrieveDataWithCredential:isReauthentication:callback:]_block_invoke_2.374", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInWithCredential:callback:]", + "coverage": 0 + }, + { + "name": "__41-[FIRAuth signInWithCredential:callback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuth signInAnonymouslyAndRetrieveDataWithCompletion:]", + "coverage": 1 + }, + { + "name": "__58-[FIRAuth signInAnonymouslyAndRetrieveDataWithCompletion:]_block_invoke", + "coverage": 0.7105263157894737 + }, + { + "name": "__58-[FIRAuth signInAnonymouslyAndRetrieveDataWithCompletion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__58-[FIRAuth signInAnonymouslyAndRetrieveDataWithCompletion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInAnonymouslyWithCompletion:]", + "coverage": 1 + }, + { + "name": "__43-[FIRAuth signInAnonymouslyWithCompletion:]_block_invoke", + "coverage": 0.84375 + }, + { + "name": "__43-[FIRAuth signInAnonymouslyWithCompletion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__43-[FIRAuth signInAnonymouslyWithCompletion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInWithCustomToken:completion:]", + "coverage": 1 + }, + { + "name": "__44-[FIRAuth signInWithCustomToken:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__44-[FIRAuth signInWithCustomToken:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInAndRetrieveDataWithCustomToken:completion:]", + "coverage": 1 + }, + { + "name": "__59-[FIRAuth signInAndRetrieveDataWithCustomToken:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth createUserWithEmail:password:completion:]", + "coverage": 1 + }, + { + "name": "__51-[FIRAuth createUserWithEmail:password:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__51-[FIRAuth createUserWithEmail:password:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__51-[FIRAuth createUserWithEmail:password:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth createUserAndRetrieveDataWithEmail:password:completion:]", + "coverage": 1 + }, + { + "name": "__66-[FIRAuth createUserAndRetrieveDataWithEmail:password:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__66-[FIRAuth createUserAndRetrieveDataWithEmail:password:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__66-[FIRAuth createUserAndRetrieveDataWithEmail:password:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth confirmPasswordResetWithCode:newPassword:completion:]", + "coverage": 1 + }, + { + "name": "__63-[FIRAuth confirmPasswordResetWithCode:newPassword:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__63-[FIRAuth confirmPasswordResetWithCode:newPassword:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__63-[FIRAuth confirmPasswordResetWithCode:newPassword:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth checkActionCode:completion:]", + "coverage": 1 + }, + { + "name": "__38-[FIRAuth checkActionCode:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__38-[FIRAuth checkActionCode:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__38-[FIRAuth checkActionCode:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "__38-[FIRAuth checkActionCode:completion:]_block_invoke.470", + "coverage": 1 + }, + { + "name": "-[FIRAuth verifyPasswordResetCode:completion:]", + "coverage": 1 + }, + { + "name": "__46-[FIRAuth verifyPasswordResetCode:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth applyActionCode:completion:]", + "coverage": 1 + }, + { + "name": "__38-[FIRAuth applyActionCode:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__38-[FIRAuth applyActionCode:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__38-[FIRAuth applyActionCode:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth sendPasswordResetWithEmail:completion:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth sendPasswordResetWithEmail:actionCodeSettings:completion:]", + "coverage": 0 + }, + { + "name": "-[FIRAuth sendPasswordResetWithNullableActionCodeSettings:email:completion:]", + "coverage": 1 + }, + { + "name": "__76-[FIRAuth sendPasswordResetWithNullableActionCodeSettings:email:completion:]_block_invoke", + "coverage": 0.8095238095238095 + }, + { + "name": "__76-[FIRAuth sendPasswordResetWithNullableActionCodeSettings:email:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__76-[FIRAuth sendPasswordResetWithNullableActionCodeSettings:email:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth sendSignInLinkToEmail:actionCodeSettings:completion:]", + "coverage": 1 + }, + { + "name": "__63-[FIRAuth sendSignInLinkToEmail:actionCodeSettings:completion:]_block_invoke", + "coverage": 0.75 + }, + { + "name": "__63-[FIRAuth sendSignInLinkToEmail:actionCodeSettings:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__63-[FIRAuth sendSignInLinkToEmail:actionCodeSettings:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRAuth updateCurrentUser:completion:]", + "coverage": 1 + }, + { + "name": "__40-[FIRAuth updateCurrentUser:completion:]_block_invoke", + "coverage": 0.9545454545454546 + }, + { + "name": "__40-[FIRAuth updateCurrentUser:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__40-[FIRAuth updateCurrentUser:completion:]_block_invoke.539", + "coverage": 0.625 + }, + { + "name": "__40-[FIRAuth updateCurrentUser:completion:]_block_invoke_2.540", + "coverage": 0 + }, + { + "name": "__40-[FIRAuth updateCurrentUser:completion:]_block_invoke.544", + "coverage": 1 + }, + { + "name": "__40-[FIRAuth updateCurrentUser:completion:]_block_invoke.556", + "coverage": 1 + }, + { + "name": "__40-[FIRAuth updateCurrentUser:completion:]_block_invoke_2.557", + "coverage": 1 + }, + { + "name": "-[FIRAuth signOut:]", + "coverage": 1 + }, + { + "name": "__19-[FIRAuth signOut:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth signOutByForceWithUserID:error:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRAuth isSignInWithEmailLink:]", + "coverage": 0.76 + }, + { + "name": "FIRAuthParseURL", + "coverage": 1 + }, + { + "name": "-[FIRAuth addAuthStateDidChangeListener:]", + "coverage": 1 + }, + { + "name": "__41-[FIRAuth addAuthStateDidChangeListener:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth removeAuthStateDidChangeListener:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth addIDTokenDidChangeListener:]", + "coverage": 0.8636363636363636 + }, + { + "name": "__39-[FIRAuth addIDTokenDidChangeListener:]_block_invoke", + "coverage": 1 + }, + { + "name": "__39-[FIRAuth addIDTokenDidChangeListener:]_block_invoke.604", + "coverage": 1 + }, + { + "name": "-[FIRAuth removeIDTokenDidChangeListener:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth useAppLanguage]", + "coverage": 0 + }, + { + "name": "__25-[FIRAuth useAppLanguage]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuth languageCode]", + "coverage": 0 + }, + { + "name": "-[FIRAuth setLanguageCode:]", + "coverage": 0 + }, + { + "name": "__27-[FIRAuth setLanguageCode:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuth additionalFrameworkMarker]", + "coverage": 0 + }, + { + "name": "-[FIRAuth setAdditionalFrameworkMarker:]", + "coverage": 0 + }, + { + "name": "__40-[FIRAuth setAdditionalFrameworkMarker:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuth APNSToken]", + "coverage": 0 + }, + { + "name": "__20-[FIRAuth APNSToken]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuth setAPNSToken:]", + "coverage": 0 + }, + { + "name": "-[FIRAuth setAPNSToken:type:]", + "coverage": 0 + }, + { + "name": "__29-[FIRAuth setAPNSToken:type:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuth handleAPNSTokenError:]", + "coverage": 0 + }, + { + "name": "__32-[FIRAuth handleAPNSTokenError:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuth canHandleNotification:]", + "coverage": 0 + }, + { + "name": "__33-[FIRAuth canHandleNotification:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuth canHandleURL:]", + "coverage": 0 + }, + { + "name": "__24-[FIRAuth canHandleURL:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuth signInWithPhoneCredential:operation:callback:]", + "coverage": 0.6923076923076923 + }, + { + "name": "-[FIRAuth internalSignInAndRetrieveDataWithCustomToken:completion:]", + "coverage": 1 + }, + { + "name": "__67-[FIRAuth internalSignInAndRetrieveDataWithCustomToken:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__67-[FIRAuth internalSignInAndRetrieveDataWithCustomToken:completion:]_block_invoke_2", + "coverage": 0.7222222222222222 + }, + { + "name": "-[FIRAuth internalCreateUserWithEmail:password:completion:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth internalSignInAnonymouslyWithCompletion:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth possiblyPostAuthStateChangeNotification]", + "coverage": 1 + }, + { + "name": "__50-[FIRAuth possiblyPostAuthStateChangeNotification]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuth updateKeychainWithUser:error:]", + "coverage": 0.8333333333333334 + }, + { + "name": "+[FIRAuth setKeychainServiceNameForApp:]", + "coverage": 1 + }, + { + "name": "+[FIRAuth keychainServiceNameForAppName:]", + "coverage": 1 + }, + { + "name": "+[FIRAuth deleteKeychainServiceNameForAppName:]", + "coverage": 0 + }, + { + "name": "-[FIRAuth scheduleAutoTokenRefresh]", + "coverage": 1 + }, + { + "name": "-[FIRAuth scheduleAutoTokenRefreshWithDelay:retry:]", + "coverage": 1 + }, + { + "name": "__51-[FIRAuth scheduleAutoTokenRefreshWithDelay:retry:]_block_invoke", + "coverage": 0.7878787878787878 + }, + { + "name": "__51-[FIRAuth scheduleAutoTokenRefreshWithDelay:retry:]_block_invoke_2", + "coverage": 0.9375 + }, + { + "name": "-[FIRAuth completeSignInWithAccessToken:accessTokenExpirationDate:refreshToken:anonymous:callback:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInFlowAuthResultCallbackByDecoratingCallback:]", + "coverage": 1 + }, + { + "name": "__60-[FIRAuth signInFlowAuthResultCallbackByDecoratingCallback:]_block_invoke", + "coverage": 0.6956521739130435 + }, + { + "name": "__60-[FIRAuth signInFlowAuthResultCallbackByDecoratingCallback:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__60-[FIRAuth signInFlowAuthResultCallbackByDecoratingCallback:]_block_invoke.765", + "coverage": 0 + }, + { + "name": "__60-[FIRAuth signInFlowAuthResultCallbackByDecoratingCallback:]_block_invoke.769", + "coverage": 1 + }, + { + "name": "-[FIRAuth signInFlowAuthDataResultCallbackByDecoratingCallback:]", + "coverage": 1 + }, + { + "name": "__64-[FIRAuth signInFlowAuthDataResultCallbackByDecoratingCallback:]_block_invoke", + "coverage": 0.6956521739130435 + }, + { + "name": "__64-[FIRAuth signInFlowAuthDataResultCallbackByDecoratingCallback:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__64-[FIRAuth signInFlowAuthDataResultCallbackByDecoratingCallback:]_block_invoke.779", + "coverage": 0 + }, + { + "name": "__64-[FIRAuth signInFlowAuthDataResultCallbackByDecoratingCallback:]_block_invoke.783", + "coverage": 1 + }, + { + "name": "-[FIRAuth updateCurrentUser:byForce:savingToDisk:error:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth saveUser:error:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth getUser:error:]", + "coverage": 0.7727272727272727 + }, + { + "name": "+[FIRAuth componentsToRegister]", + "coverage": 1 + }, + { + "name": "__31+[FIRAuth componentsToRegister]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRAuth configureWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRAuth appWillBeDeleted:]", + "coverage": 0 + }, + { + "name": "__28-[FIRAuth appWillBeDeleted:]_block_invoke", + "coverage": 0 + }, + { + "name": "__28-[FIRAuth appWillBeDeleted:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRAuth getTokenForcingRefresh:withCallback:]", + "coverage": 1 + }, + { + "name": "__47-[FIRAuth getTokenForcingRefresh:withCallback:]_block_invoke", + "coverage": 0.82 + }, + { + "name": "__47-[FIRAuth getTokenForcingRefresh:withCallback:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__47-[FIRAuth getTokenForcingRefresh:withCallback:]_block_invoke.837", + "coverage": 0 + }, + { + "name": "__47-[FIRAuth getTokenForcingRefresh:withCallback:]_block_invoke.841", + "coverage": 1 + }, + { + "name": "__47-[FIRAuth getTokenForcingRefresh:withCallback:]_block_invoke.845", + "coverage": 0 + }, + { + "name": "__47-[FIRAuth getTokenForcingRefresh:withCallback:]_block_invoke_2.846", + "coverage": 0 + }, + { + "name": "-[FIRAuth getUserID]", + "coverage": 1 + } + ] + }, + { + "name": "FIRSecureTokenService.m", + "coverage": 0.8592592592592593, + "type": "objc", + "functions": [ + { + "name": "-[FIRSecureTokenService init]", + "coverage": 1 + }, + { + "name": "-[FIRSecureTokenService initWithRequestConfiguration:authorizationCode:]", + "coverage": 0 + }, + { + "name": "-[FIRSecureTokenService initWithRequestConfiguration:accessToken:accessTokenExpirationDate:refreshToken:]", + "coverage": 1 + }, + { + "name": "-[FIRSecureTokenService fetchAccessTokenForcingRefresh:callback:]", + "coverage": 1 + }, + { + "name": "__65-[FIRSecureTokenService fetchAccessTokenForcingRefresh:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "__65-[FIRSecureTokenService fetchAccessTokenForcingRefresh:callback:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRSecureTokenService rawAccessToken]", + "coverage": 1 + }, + { + "name": "+[FIRSecureTokenService supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRSecureTokenService initWithCoder:]", + "coverage": 0.875 + }, + { + "name": "-[FIRSecureTokenService encodeWithCoder:]", + "coverage": 1 + }, + { + "name": "-[FIRSecureTokenService requestAccessToken:]", + "coverage": 0.8928571428571429 + }, + { + "name": "__44-[FIRSecureTokenService requestAccessToken:]_block_invoke", + "coverage": 0.8125 + }, + { + "name": "-[FIRSecureTokenService hasValidAccessToken]", + "coverage": 1 + } + ] + }, + { + "name": "FIRUserMetadata.m", + "coverage": 0.8636363636363636, + "type": "objc", + "functions": [ + { + "name": "-[FIRUserMetadata initWithCreationDate:lastSignInDate:]", + "coverage": 1 + }, + { + "name": "+[FIRUserMetadata supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRUserMetadata initWithCoder:]", + "coverage": 1 + }, + { + "name": "-[FIRUserMetadata encodeWithCoder:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRGetAccountInfoResponse.m", + "coverage": 0.875, + "type": "objc", + "functions": [ + { + "name": "-[FIRGetAccountInfoResponseProviderUserInfo initWithDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIRGetAccountInfoResponseUser initWithDictionary:]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FIRGetAccountInfoResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRApp.m", + "coverage": 0.8848, + "type": "objc", + "functions": [ + { + "name": "+[FIRApp configure]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithOptions:]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithName:options:]", + "coverage": 0.9333333333333333 + }, + { + "name": "+[FIRApp defaultApp]", + "coverage": 1 + }, + { + "name": "+[FIRApp appNamed:]", + "coverage": 1 + }, + { + "name": "+[FIRApp allApps]", + "coverage": 1 + }, + { + "name": "+[FIRApp resetApps]", + "coverage": 1 + }, + { + "name": "-[FIRApp deleteApp:]", + "coverage": 0.7916666666666666 + }, + { + "name": "+[FIRApp addAppToAppDictionary:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRApp initInstanceWithName:options:]", + "coverage": 1 + }, + { + "name": "-[FIRApp configureCore]", + "coverage": 0.7962962962962963 + }, + { + "name": "-[FIRApp options]", + "coverage": 1 + }, + { + "name": "-[FIRApp setDataCollectionDefaultEnabled:]", + "coverage": 0.9354838709677419 + }, + { + "name": "-[FIRApp isDataCollectionDefaultEnabled]", + "coverage": 1 + }, + { + "name": "+[FIRApp sendNotificationsToSDKs:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForMissingOptions]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForSubspecConfigurationFailureWithDomain:errorCode:service:reason:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForInvalidAppID]", + "coverage": 0 + }, + { + "name": "+[FIRApp isDefaultAppConfigured]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerLibrary:withVersion:]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerInternalLibrary:withName:withVersion:]", + "coverage": 1 + }, + { + "name": "__55+[FIRApp registerInternalLibrary:withName:withVersion:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRApp firebaseUserAgent]", + "coverage": 1 + }, + { + "name": "-[FIRApp checkExpectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp isAppIDValid]", + "coverage": 1 + }, + { + "name": "+[FIRApp validateAppID:]", + "coverage": 0.9473684210526315 + }, + { + "name": "+[FIRApp actualBundleID]", + "coverage": 0 + }, + { + "name": "+[FIRApp validateAppIDFormat:withVersion:]", + "coverage": 0.8450704225352113 + }, + { + "name": "+[FIRApp validateAppIDFingerprint:withVersion:]", + "coverage": 0.8620689655172413 + }, + { + "name": "-[FIRApp expectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp clearDataCollectionSwitchFromUserDefaults]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromUserDefaultsForApp:]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromPlist]", + "coverage": 1 + }, + { + "name": "__43+[FIRApp readDataCollectionSwitchFromPlist]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRApp sendLogsWithServiceName:version:error:]", + "coverage": 0.2631578947368421 + } + ] + }, + { + "name": "FIRAuthBackend.m", + "coverage": 0.9025755879059351, + "type": "objc", + "functions": [ + { + "name": "+[FIRAuthBackend implementation]", + "coverage": 0.6666666666666666 + }, + { + "name": "+[FIRAuthBackend setBackendImplementation:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend createAuthURI:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend getAccountInfo:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend getProjectConfig:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend setAccountInfo:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend verifyAssertion:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend verifyCustomToken:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend verifyPassword:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend emailLinkSignin:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend secureToken:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend getOOBConfirmationCode:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend signUpNewUser:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend deleteAccount:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend signInWithGameCenter:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend sendVerificationCode:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend verifyPhoneNumber:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend verifyClient:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend resetPassword:callback:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthBackend authUserAgent]", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCIssuerImplementation init]", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCIssuerImplementation asyncPostToURLWithRequestConfiguration:URL:body:contentType:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[FIRAuthBackendRPCImplementation init]", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation createAuthURI:callback:]", + "coverage": 1 + }, + { + "name": "__58-[FIRAuthBackendRPCImplementation createAuthURI:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation getAccountInfo:callback:]", + "coverage": 1 + }, + { + "name": "__59-[FIRAuthBackendRPCImplementation getAccountInfo:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation getProjectConfig:callback:]", + "coverage": 1 + }, + { + "name": "__61-[FIRAuthBackendRPCImplementation getProjectConfig:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation setAccountInfo:callback:]", + "coverage": 1 + }, + { + "name": "__59-[FIRAuthBackendRPCImplementation setAccountInfo:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation verifyAssertion:callback:]", + "coverage": 1 + }, + { + "name": "__60-[FIRAuthBackendRPCImplementation verifyAssertion:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation verifyCustomToken:callback:]", + "coverage": 1 + }, + { + "name": "__62-[FIRAuthBackendRPCImplementation verifyCustomToken:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation verifyPassword:callback:]", + "coverage": 1 + }, + { + "name": "__59-[FIRAuthBackendRPCImplementation verifyPassword:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation emailLinkSignin:callback:]", + "coverage": 1 + }, + { + "name": "__60-[FIRAuthBackendRPCImplementation emailLinkSignin:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation secureToken:callback:]", + "coverage": 0 + }, + { + "name": "__56-[FIRAuthBackendRPCImplementation secureToken:callback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRAuthBackendRPCImplementation getOOBConfirmationCode:callback:]", + "coverage": 1 + }, + { + "name": "__67-[FIRAuthBackendRPCImplementation getOOBConfirmationCode:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation signUpNewUser:callback:]", + "coverage": 1 + }, + { + "name": "__58-[FIRAuthBackendRPCImplementation signUpNewUser:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation deleteAccount:callback:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation sendVerificationCode:callback:]", + "coverage": 1 + }, + { + "name": "__65-[FIRAuthBackendRPCImplementation sendVerificationCode:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation verifyPhoneNumber:callback:]", + "coverage": 1 + }, + { + "name": "__62-[FIRAuthBackendRPCImplementation verifyPhoneNumber:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation verifyClient:callback:]", + "coverage": 1 + }, + { + "name": "__57-[FIRAuthBackendRPCImplementation verifyClient:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation resetPassword:callback:]", + "coverage": 1 + }, + { + "name": "__58-[FIRAuthBackendRPCImplementation resetPassword:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthBackendRPCImplementation signInWithGameCenter:callback:]", + "coverage": 1 + }, + { + "name": "__65-[FIRAuthBackendRPCImplementation signInWithGameCenter:callback:]_block_invoke", + "coverage": 0.7272727272727273 + }, + { + "name": "-[FIRAuthBackendRPCImplementation postWithRequest:response:callback:]", + "coverage": 0.9624060150375939 + }, + { + "name": "__69-[FIRAuthBackendRPCImplementation postWithRequest:response:callback:]_block_invoke", + "coverage": 0.9148936170212766 + }, + { + "name": "+[FIRAuthBackendRPCImplementation clientErrorWithServerErrorMessage:errorDictionary:response:]", + "coverage": 0.8870292887029289 + } + ] + }, + { + "name": "FIRAuthErrorUtils.m", + "coverage": 0.9050736497545008, + "type": "objc", + "functions": [ + { + "name": "FIRAuthErrorDescription", + "coverage": 0.9047619047619048 + }, + { + "name": "FIRAuthErrorCodeString", + "coverage": 0.9444444444444444 + }, + { + "name": "+[FIRAuthErrorUtils errorWithCode:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils errorWithCode:message:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils errorWithCode:underlyingError:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils errorWithCode:userInfo:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils RPCRequestEncodingErrorWithUnderlyingError:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils JSONSerializationErrorForUnencodableType]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:]", + "coverage": 0 + }, + { + "name": "+[FIRAuthErrorUtils networkErrorWithUnderlyingError:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils unexpectedErrorResponseWithData:underlyingError:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils malformedJWTErrorWithToken:underlyingError:]", + "coverage": 0 + }, + { + "name": "+[FIRAuthErrorUtils unexpectedResponseWithData:underlyingError:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:underlyingError:]", + "coverage": 0.8 + }, + { + "name": "+[FIRAuthErrorUtils RPCResponseDecodingErrorWithDeserializedResponse:underlyingError:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils emailAlreadyInUseErrorWithEmail:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils userDisabledErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils wrongPasswordErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils tooManyRequestsErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidCustomTokenErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils customTokenMistmatchErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidCredentialErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils requiresRecentLoginErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidUserTokenErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidEmailErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils providerAlreadyLinkedError]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils noSuchProviderError]", + "coverage": 0 + }, + { + "name": "+[FIRAuthErrorUtils userTokenExpiredErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils userNotFoundErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidAPIKeyError]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils userMismatchError]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils credentialAlreadyInUseErrorWithMessage:credential:email:]", + "coverage": 0.8947368421052632 + }, + { + "name": "+[FIRAuthErrorUtils operationNotAllowedErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils appNotAuthorizedError]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils expiredActionCodeErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidActionCodeErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidMessagePayloadErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidSenderErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidRecipientEmailErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils missingIosBundleIDErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils missingAndroidPackageNameErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils unauthorizedDomainErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidContinueURIErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils missingContinueURIErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils missingEmailErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils missingPhoneNumberErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils missingVerificationCodeErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidVerificationCodeErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils missingVerificationIDErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidVerificationIDErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils sessionExpiredErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils missingAppCredentialWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidAppCredentialWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils quotaExceededErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils missingAppTokenErrorWithUnderlyingError:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils localPlayerNotAuthenticatedError]", + "coverage": 0 + }, + { + "name": "+[FIRAuthErrorUtils gameKitNotLinkedError]", + "coverage": 0 + }, + { + "name": "+[FIRAuthErrorUtils notificationNotForwardedError]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils appNotVerifiedErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils captchaCheckFailedErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils webContextAlreadyPresentedErrorWithMessage:]", + "coverage": 0 + }, + { + "name": "+[FIRAuthErrorUtils webContextCancelledErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils appVerificationUserInteractionFailureWithReason:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils webSignInUserInteractionFailureWithReason:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils URLResponseErrorWithCode:message:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils nullUserErrorWithMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthErrorUtils invalidDynamicLinkDomainErrorWithMessage:]", + "coverage": 0 + }, + { + "name": "+[FIRAuthErrorUtils keychainErrorWithFunction:status:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthAppCredential.m", + "coverage": 0.9130434782608695, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthAppCredential initWithReceipt:secret:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthAppCredential supportsSecureCoding]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppCredential initWithCoder:]", + "coverage": 0.75 + }, + { + "name": "-[FIRAuthAppCredential encodeWithCoder:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAdditionalUserInfo.m", + "coverage": 0.925, + "type": "objc", + "functions": [ + { + "name": "+[FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:]", + "coverage": 1 + }, + { + "name": "-[FIRAdditionalUserInfo initWithProviderID:profile:username:isNewUser:]", + "coverage": 1 + }, + { + "name": "+[FIRAdditionalUserInfo supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRAdditionalUserInfo initWithCoder:]", + "coverage": 1 + }, + { + "name": "-[FIRAdditionalUserInfo encodeWithCoder:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRVerifyPhoneNumberRequest.m", + "coverage": 0.9272727272727272, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyPhoneNumberRequest initWithTemporaryProof:phoneNumber:operation:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRVerifyPhoneNumberRequest initWithVerificationID:verificationCode:operation:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "FIRAuthOperationString", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FIRVerifyPhoneNumberRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRUserInfoImpl.m", + "coverage": 0.9387755102040817, + "type": "objc", + "functions": [ + { + "name": "+[FIRUserInfoImpl userInfoWithGetAccountInfoResponseProviderUserInfo:]", + "coverage": 1 + }, + { + "name": "-[FIRUserInfoImpl initWithProviderID:userID:displayName:photoURL:email:phoneNumber:]", + "coverage": 1 + }, + { + "name": "+[FIRUserInfoImpl supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[FIRUserInfoImpl initWithCoder:]", + "coverage": 1 + }, + { + "name": "-[FIRUserInfoImpl encodeWithCoder:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthNotificationManager.m", + "coverage": 0.9391304347826087, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthNotificationManager initWithApplication:appCredentialManager:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthNotificationManager checkNotificationForwardingWithCallback:]", + "coverage": 1 + }, + { + "name": "__70-[FIRAuthNotificationManager checkNotificationForwardingWithCallback:]_block_invoke", + "coverage": 0.8260869565217391 + }, + { + "name": "__70-[FIRAuthNotificationManager checkNotificationForwardingWithCallback:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__70-[FIRAuthNotificationManager checkNotificationForwardingWithCallback:]_block_invoke.33", + "coverage": 1 + }, + { + "name": "-[FIRAuthNotificationManager canHandleNotification:]", + "coverage": 0.9310344827586207 + }, + { + "name": "-[FIRAuthNotificationManager callBack]", + "coverage": 1 + } + ] + }, + { + "name": "FIROptions.m", + "coverage": 0.9489795918367347, + "type": "objc", + "functions": [ + { + "name": "+[FIROptions defaultOptions]", + "coverage": 0.8461538461538461 + }, + { + "name": "+[FIROptions initialize]", + "coverage": 1 + }, + { + "name": "+[FIROptions defaultOptionsDictionary]", + "coverage": 1 + }, + { + "name": "+[FIROptions plistFilePathWithName:]", + "coverage": 1 + }, + { + "name": "+[FIROptions resetDefaultOptions]", + "coverage": 1 + }, + { + "name": "-[FIROptions initInternalWithOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithContentsOfFile:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithGoogleAppID:GCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions APIKey]", + "coverage": 1 + }, + { + "name": "-[FIROptions checkEditingLocked]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAPIKey:]", + "coverage": 1 + }, + { + "name": "-[FIROptions clientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions trackingID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setTrackingID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions GCMSenderID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions projectID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setProjectID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions androidClientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAndroidClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions googleAppID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGoogleAppID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions libraryVersionID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setLibraryVersionID:]", + "coverage": 0 + }, + { + "name": "-[FIROptions databaseURL]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDatabaseURL:]", + "coverage": 1 + }, + { + "name": "-[FIROptions storageBucket]", + "coverage": 1 + }, + { + "name": "-[FIROptions setStorageBucket:]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDeepLinkURLScheme:]", + "coverage": 1 + }, + { + "name": "-[FIROptions bundleID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionaryWithInfoDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIROptions isMeasurementEnabled]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIROptions isAnalyticsCollectionExpicitlySet]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionEnabled]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionDeactivated]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsEnabled]", + "coverage": 0 + }, + { + "name": "-[FIROptions isSignInEnabled]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAuthWebUtils.m", + "coverage": 0.9507042253521126, + "type": "objc", + "functions": [ + { + "name": "+[FIRAuthWebUtils randomStringWithLength:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthWebUtils isExpectedCallbackURL:eventID:authType:callbackScheme:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthWebUtils fetchAuthDomainWithRequestConfiguration:completion:]", + "coverage": 1 + }, + { + "name": "__70+[FIRAuthWebUtils fetchAuthDomainWithRequestConfiguration:completion:]_block_invoke", + "coverage": 0.7142857142857143 + }, + { + "name": "+[FIRAuthWebUtils queryItemValue:from:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthWebUtils dictionaryWithHttpArgumentsString:]", + "coverage": 0.9642857142857143 + }, + { + "name": "+[FIRAuthWebUtils stringByUnescapingFromURLArgument:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAnalyticsConfiguration.m", + "coverage": 0.9545454545454546, + "type": "objc", + "functions": [ + { + "name": "+[FIRAnalyticsConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__43+[FIRAnalyticsConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration postNotificationName:value:]", + "coverage": 0.75 + }, + { + "name": "-[FIRAnalyticsConfiguration setMinimumSessionInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setSessionTimeoutInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:persistSetting:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRPhoneAuthProvider.m", + "coverage": 0.9618320610687023, + "type": "objc", + "functions": [ + { + "name": "-[FIRPhoneAuthProvider initWithAuth:]", + "coverage": 1 + }, + { + "name": "-[FIRPhoneAuthProvider verifyPhoneNumber:UIDelegate:completion:]", + "coverage": 1 + }, + { + "name": "__64-[FIRPhoneAuthProvider verifyPhoneNumber:UIDelegate:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__64-[FIRPhoneAuthProvider verifyPhoneNumber:UIDelegate:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__64-[FIRPhoneAuthProvider verifyPhoneNumber:UIDelegate:completion:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "__64-[FIRPhoneAuthProvider verifyPhoneNumber:UIDelegate:completion:]_block_invoke.33", + "coverage": 1 + }, + { + "name": "__64-[FIRPhoneAuthProvider verifyPhoneNumber:UIDelegate:completion:]_block_invoke_2.42", + "coverage": 0.9302325581395349 + }, + { + "name": "__64-[FIRPhoneAuthProvider verifyPhoneNumber:UIDelegate:completion:]_block_invoke_3.43", + "coverage": 1 + }, + { + "name": "__64-[FIRPhoneAuthProvider verifyPhoneNumber:UIDelegate:completion:]_block_invoke.52", + "coverage": 1 + }, + { + "name": "__64-[FIRPhoneAuthProvider verifyPhoneNumber:UIDelegate:completion:]_block_invoke_2.63", + "coverage": 0.5714285714285714 + }, + { + "name": "-[FIRPhoneAuthProvider credentialWithVerificationID:verificationCode:]", + "coverage": 1 + }, + { + "name": "+[FIRPhoneAuthProvider provider]", + "coverage": 1 + }, + { + "name": "+[FIRPhoneAuthProvider providerWithAuth:]", + "coverage": 1 + }, + { + "name": "-[FIRPhoneAuthProvider reCAPTCHATokenForURL:error:]", + "coverage": 0.8717948717948718 + }, + { + "name": "-[FIRPhoneAuthProvider internalVerifyPhoneNumber:completion:]", + "coverage": 1 + }, + { + "name": "__61-[FIRPhoneAuthProvider internalVerifyPhoneNumber:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__61-[FIRPhoneAuthProvider internalVerifyPhoneNumber:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRPhoneAuthProvider verifyClientAndSendVerificationCodeToPhoneNumber:retryOnInvalidAppCredential:callback:]", + "coverage": 1 + }, + { + "name": "__110-[FIRPhoneAuthProvider verifyClientAndSendVerificationCodeToPhoneNumber:retryOnInvalidAppCredential:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "__110-[FIRPhoneAuthProvider verifyClientAndSendVerificationCodeToPhoneNumber:retryOnInvalidAppCredential:callback:]_block_invoke.163", + "coverage": 1 + }, + { + "name": "__110-[FIRPhoneAuthProvider verifyClientAndSendVerificationCodeToPhoneNumber:retryOnInvalidAppCredential:callback:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRPhoneAuthProvider verifyClientWithCompletion:]", + "coverage": 1 + }, + { + "name": "__51-[FIRPhoneAuthProvider verifyClientWithCompletion:]_block_invoke", + "coverage": 1 + }, + { + "name": "__51-[FIRPhoneAuthProvider verifyClientWithCompletion:]_block_invoke_2", + "coverage": 0.8333333333333334 + }, + { + "name": "__51-[FIRPhoneAuthProvider verifyClientWithCompletion:]_block_invoke_3", + "coverage": 0.5 + }, + { + "name": "-[FIRPhoneAuthProvider reCAPTCHAURLWithEventID:completion:]", + "coverage": 1 + }, + { + "name": "__59-[FIRPhoneAuthProvider reCAPTCHAURLWithEventID:completion:]_block_invoke", + "coverage": 0.7586206896551724 + } + ] + }, + { + "name": "FIRVerifyAssertionRequest.m", + "coverage": 0.9710144927536232, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyAssertionRequest initWithProviderID:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRVerifyAssertionRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 0.9655172413793104 + } + ] + }, + { + "name": "FIRComponentContainer.m", + "coverage": 0.9818181818181818, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:]", + "coverage": 1 + }, + { + "name": "__55+[FIRComponentContainer registerAsComponentRegistrant:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:inSet:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:registrants:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer populateComponentsFromRegisteredClasses:forApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer instantiateInstanceForProtocol:withBlock:]", + "coverage": 0.9285714285714286 + }, + { + "name": "-[FIRComponentContainer instanceForProtocol:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer removeAllCachedInstances]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthAppDelegateProxy.m", + "coverage": 0.9820359281437125, + "type": "objc", + "functions": [ + { + "name": "noop", + "coverage": 1 + }, + { + "name": "isIOS9orLater", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRAuthAppDelegateProxy initWithApplication:]", + "coverage": 1 + }, + { + "name": "__47-[FIRAuthAppDelegateProxy initWithApplication:]_block_invoke", + "coverage": 1 + }, + { + "name": "__47-[FIRAuthAppDelegateProxy initWithApplication:]_block_invoke.167", + "coverage": 1 + }, + { + "name": "__47-[FIRAuthAppDelegateProxy initWithApplication:]_block_invoke.177", + "coverage": 1 + }, + { + "name": "__47-[FIRAuthAppDelegateProxy initWithApplication:]_block_invoke.184", + "coverage": 1 + }, + { + "name": "__47-[FIRAuthAppDelegateProxy initWithApplication:]_block_invoke.194", + "coverage": 1 + }, + { + "name": "__47-[FIRAuthAppDelegateProxy initWithApplication:]_block_invoke.201", + "coverage": 1 + }, + { + "name": "__47-[FIRAuthAppDelegateProxy initWithApplication:]_block_invoke.208", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy addHandler:]", + "coverage": 1 + }, + { + "name": "+[FIRAuthAppDelegateProxy sharedInstance]", + "coverage": 1 + }, + { + "name": "__41+[FIRAuthAppDelegateProxy sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy object:selector:application:didRegisterForRemoteNotificationsWithDeviceToken:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy object:selector:application:didFailToRegisterForRemoteNotificationsWithError:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy object:selector:application:didReceiveRemoteNotification:fetchCompletionHandler:]", + "coverage": 0.9411764705882353 + }, + { + "name": "-[FIRAuthAppDelegateProxy object:selector:application:didReceiveRemoteNotification:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy object:selector:application:openURL:options:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy object:selector:application:openURL:sourceApplication:annotation:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRAuthAppDelegateProxy object:selector:application:handleOpenURL:]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIRAuthAppDelegateProxy delegateCanHandleURL:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy handlers]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy replaceSelector:withBlock:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppDelegateProxy originalImplementationForSelector:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRVerifyAssertionResponse.m", + "coverage": 0.9827586206896551, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyAssertionResponse setWithDictionary:error:]", + "coverage": 0.9827586206896551 + } + ] + }, + { + "name": "FIRGetOOBConfirmationCodeRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRGetOOBConfirmationCodeRequest requestTypeStringValueForRequestType:]", + "coverage": 1 + }, + { + "name": "+[FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:actionCodeSettings:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "+[FIRGetOOBConfirmationCodeRequest verifyEmailRequestWithAccessToken:actionCodeSettings:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "+[FIRGetOOBConfirmationCodeRequest signInWithEmailLinkRequest:actionCodeSettings:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRGetOOBConfirmationCodeRequest initWithRequestType:email:accessToken:actionCodeSettings:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRGetOOBConfirmationCodeRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRGetOOBConfirmationCodeResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRGetOOBConfirmationCodeResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRGetProjectConfigRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRGetProjectConfigRequest initWithRequestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRGetProjectConfigRequest containsPostBody]", + "coverage": 1 + } + ] + }, + { + "name": "NSData+FIRBase64.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[NSData(FIRBase64) fir_base64URLEncodedStringWithOptions:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRResetPasswordRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRResetPasswordRequest initWithOobCode:newPassword:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRResetPasswordRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRResetPasswordResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRResetPasswordResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthUserDefaultsStorage.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthUserDefaultsStorage initWithService:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthUserDefaultsStorage dataForKey:error:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthUserDefaultsStorage setData:forKey:error:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthUserDefaultsStorage removeDataForKey:error:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthUserDefaultsStorage clear]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthSettings.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthSettings init]", + "coverage": 1 + } + ] + }, + { + "name": "FIRSendVerificationCodeRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRSendVerificationCodeRequest initWithPhoneNumber:appCredential:reCAPTCHAToken:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRSendVerificationCodeRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthSerialTaskQueue.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthSerialTaskQueue init]", + "coverage": 1 + }, + { + "name": "-[FIRAuthSerialTaskQueue enqueueTask:]", + "coverage": 1 + }, + { + "name": "__38-[FIRAuthSerialTaskQueue enqueueTask:]_block_invoke", + "coverage": 1 + }, + { + "name": "__38-[FIRAuthSerialTaskQueue enqueueTask:]_block_invoke_2", + "coverage": 1 + } + ] + }, + { + "name": "FIRSetAccountInfoRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRSetAccountInfoRequest initWithRequestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRSetAccountInfoRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRSetAccountInfoResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRSetAccountInfoResponseProviderUserInfo initWithDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIRSetAccountInfoResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRSignInWithGameCenterRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRSignInWithGameCenterRequest initWithPlayerID:publicKeyURL:signature:salt:timestamp:displayName:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRSignInWithGameCenterRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRSignInWithGameCenterResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRSignInWithGameCenterResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRSignUpNewUserRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRSignUpNewUserRequest initWithEmail:password:displayName:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRSignUpNewUserRequest initWithRequestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRSignUpNewUserRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRSignUpNewUserResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRSignUpNewUserResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRVerifyClientRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyClientRequest initWithAppToken:isSandbox:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRVerifyClientRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRCreateAuthURIResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRCreateAuthURIResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRVerifyCustomTokenRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyCustomTokenRequest initWithToken:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRVerifyCustomTokenRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRVerifyCustomTokenResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyCustomTokenResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRVerifyPasswordRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyPasswordRequest initWithEmail:password:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRVerifyPasswordRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRVerifyPasswordResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyPasswordResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthGlobalWorkQueue.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "FIRAuthGlobalWorkQueue", + "coverage": 1 + }, + { + "name": "__FIRAuthGlobalWorkQueue_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthDispatcher.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRAuthDispatcher sharedInstance]", + "coverage": 1 + }, + { + "name": "__35+[FIRAuthDispatcher sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthDispatcher dispatchAfterDelay:queue:task:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAppAssociationRegistration.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRAppAssociationRegistration registeredObjectWithHost:key:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthAppCredentialManager.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthAppCredentialManager initWithKeychain:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppCredentialManager maximumNumberOfPendingReceipts]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppCredentialManager didStartVerificationWithReceipt:timeout:callback:]", + "coverage": 1 + }, + { + "name": "__80-[FIRAuthAppCredentialManager didStartVerificationWithReceipt:timeout:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppCredentialManager canFinishVerificationWithReceipt:secret:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppCredentialManager clearCredential]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppCredentialManager saveData]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAppCredentialManager callBackWithReceipt:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponent.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponent componentWithProtocol:creationBlock:]", + "coverage": 1 + }, + { + "name": "+[FIRComponent componentWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + }, + { + "name": "-[FIRComponent initWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentType.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentType instanceForProtocol:inContainer:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthAPNSToken.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthAPNSToken initWithData:type:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthAPNSToken string]", + "coverage": 1 + } + ] + }, + { + "name": "FIRActionCodeSettings.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRActionCodeSettings init]", + "coverage": 1 + }, + { + "name": "-[FIRActionCodeSettings setIOSBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIRActionCodeSettings setAndroidPackageName:installIfNotAvailable:minimumVersion:]", + "coverage": 1 + } + ] + }, + { + "name": "main.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "main", + "coverage": 1 + } + ] + }, + { + "name": "FIRVerifyClientResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRVerifyClientResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRDeleteAccountRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRDeleteAccountRequest initWitLocalID:accessToken:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRDeleteAccountRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRDeleteAccountResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRDeleteAccountResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIREmailLinkSignInRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIREmailLinkSignInRequest initWithEmail:oobCode:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIREmailLinkSignInRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIREmailLinkSignInResponse.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIREmailLinkSignInResponse setWithDictionary:error:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRGetAccountInfoRequest.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRGetAccountInfoRequest initWithAccessToken:requestConfiguration:]", + "coverage": 1 + }, + { + "name": "-[FIRGetAccountInfoRequest unencodedHTTPRequestBodyWithError:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthRequestConfiguration.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthRequestConfiguration initWithAPIKey:]", + "coverage": 1 + } + ] + } + ] + }, + { + "name": "Core_Example_iOS.app", + "coverage": 0.8861788617886179, + "files": [ + { + "name": "FIRAppDelegate.m", + "coverage": 0.26666666666666666, + "type": "objc", + "functions": [ + { + "name": "-[FIRAppDelegate application:didFinishLaunchingWithOptions:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillResignActive:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidEnterBackground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationWillEnterForeground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidBecomeActive:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillTerminate:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRViewController.m", + "coverage": 0.5, + "type": "objc", + "functions": [ + { + "name": "-[FIRViewController viewDidLoad]", + "coverage": 1 + }, + { + "name": "-[FIRViewController didReceiveMemoryWarning]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDependency.m", + "coverage": 0.7857142857142857, + "type": "objc", + "functions": [ + { + "name": "+[FIRDependency dependencyWithProtocol:]", + "coverage": 0 + }, + { + "name": "+[FIRDependency dependencyWithProtocol:isRequired:]", + "coverage": 1 + }, + { + "name": "-[FIRDependency initWithProtocol:isRequired:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRBundleUtil.m", + "coverage": 0.7872340425531915, + "type": "objc", + "functions": [ + { + "name": "+[FIRBundleUtil relevantBundles]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil optionsDictionaryPathWithResourceName:andFileType:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil relevantURLSchemes]", + "coverage": 0 + }, + { + "name": "+[FIRBundleUtil hasBundleIdentifierPrefix:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil bundleIdentifierByRemovingLastPartFrom:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRConfiguration.m", + "coverage": 0.8076923076923077, + "type": "objc", + "functions": [ + { + "name": "+[FIRConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__34+[FIRConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration init]", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration setLoggerLevel:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRLogger.m", + "coverage": 0.8118811881188119, + "type": "objc", + "functions": [ + { + "name": "FIRLoggerInitializeASL", + "coverage": 1 + }, + { + "name": "__FIRLoggerInitializeASL_block_invoke", + "coverage": 0.9333333333333333 + }, + { + "name": "FIRSetAnalyticsDebugMode", + "coverage": 0 + }, + { + "name": "FIRSetLoggerLevel", + "coverage": 0 + }, + { + "name": "FIRResetLogger", + "coverage": 1 + }, + { + "name": "FIRSetLoggerUserDefaults", + "coverage": 1 + }, + { + "name": "FIRIsLoggableLevel", + "coverage": 0 + }, + { + "name": "FIRLogBasic", + "coverage": 1 + }, + { + "name": "Definition at 158:95", + "coverage": 1 + }, + { + "name": "+[FIRLoggerWrapper logWithLevel:withService:withCode:withMessage:withArgs:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRApp.m", + "coverage": 0.8848, + "type": "objc", + "functions": [ + { + "name": "+[FIRApp configure]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithOptions:]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithName:options:]", + "coverage": 0.9333333333333333 + }, + { + "name": "+[FIRApp defaultApp]", + "coverage": 1 + }, + { + "name": "+[FIRApp appNamed:]", + "coverage": 1 + }, + { + "name": "+[FIRApp allApps]", + "coverage": 1 + }, + { + "name": "+[FIRApp resetApps]", + "coverage": 1 + }, + { + "name": "-[FIRApp deleteApp:]", + "coverage": 0.7916666666666666 + }, + { + "name": "+[FIRApp addAppToAppDictionary:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRApp initInstanceWithName:options:]", + "coverage": 1 + }, + { + "name": "-[FIRApp configureCore]", + "coverage": 0.7962962962962963 + }, + { + "name": "-[FIRApp options]", + "coverage": 1 + }, + { + "name": "-[FIRApp setDataCollectionDefaultEnabled:]", + "coverage": 0.9354838709677419 + }, + { + "name": "-[FIRApp isDataCollectionDefaultEnabled]", + "coverage": 1 + }, + { + "name": "+[FIRApp sendNotificationsToSDKs:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForMissingOptions]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForSubspecConfigurationFailureWithDomain:errorCode:service:reason:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForInvalidAppID]", + "coverage": 0 + }, + { + "name": "+[FIRApp isDefaultAppConfigured]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerLibrary:withVersion:]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerInternalLibrary:withName:withVersion:]", + "coverage": 1 + }, + { + "name": "__55+[FIRApp registerInternalLibrary:withName:withVersion:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRApp firebaseUserAgent]", + "coverage": 1 + }, + { + "name": "-[FIRApp checkExpectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp isAppIDValid]", + "coverage": 1 + }, + { + "name": "+[FIRApp validateAppID:]", + "coverage": 0.9473684210526315 + }, + { + "name": "+[FIRApp actualBundleID]", + "coverage": 0 + }, + { + "name": "+[FIRApp validateAppIDFormat:withVersion:]", + "coverage": 0.8450704225352113 + }, + { + "name": "+[FIRApp validateAppIDFingerprint:withVersion:]", + "coverage": 0.8620689655172413 + }, + { + "name": "-[FIRApp expectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp clearDataCollectionSwitchFromUserDefaults]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromUserDefaultsForApp:]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromPlist]", + "coverage": 1 + }, + { + "name": "__43+[FIRApp readDataCollectionSwitchFromPlist]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRApp sendLogsWithServiceName:version:error:]", + "coverage": 0.2631578947368421 + } + ] + }, + { + "name": "FIROptions.m", + "coverage": 0.9489795918367347, + "type": "objc", + "functions": [ + { + "name": "+[FIROptions defaultOptions]", + "coverage": 0.8461538461538461 + }, + { + "name": "+[FIROptions initialize]", + "coverage": 1 + }, + { + "name": "+[FIROptions defaultOptionsDictionary]", + "coverage": 1 + }, + { + "name": "+[FIROptions plistFilePathWithName:]", + "coverage": 1 + }, + { + "name": "+[FIROptions resetDefaultOptions]", + "coverage": 1 + }, + { + "name": "-[FIROptions initInternalWithOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithContentsOfFile:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithGoogleAppID:GCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions APIKey]", + "coverage": 1 + }, + { + "name": "-[FIROptions checkEditingLocked]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAPIKey:]", + "coverage": 1 + }, + { + "name": "-[FIROptions clientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions trackingID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setTrackingID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions GCMSenderID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions projectID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setProjectID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions androidClientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAndroidClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions googleAppID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGoogleAppID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions libraryVersionID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setLibraryVersionID:]", + "coverage": 0 + }, + { + "name": "-[FIROptions databaseURL]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDatabaseURL:]", + "coverage": 1 + }, + { + "name": "-[FIROptions storageBucket]", + "coverage": 1 + }, + { + "name": "-[FIROptions setStorageBucket:]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDeepLinkURLScheme:]", + "coverage": 1 + }, + { + "name": "-[FIROptions bundleID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionaryWithInfoDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIROptions isMeasurementEnabled]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIROptions isAnalyticsCollectionExpicitlySet]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionEnabled]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionDeactivated]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsEnabled]", + "coverage": 0 + }, + { + "name": "-[FIROptions isSignInEnabled]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAnalyticsConfiguration.m", + "coverage": 0.9545454545454546, + "type": "objc", + "functions": [ + { + "name": "+[FIRAnalyticsConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__43+[FIRAnalyticsConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration postNotificationName:value:]", + "coverage": 0.75 + }, + { + "name": "-[FIRAnalyticsConfiguration setMinimumSessionInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setSessionTimeoutInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:persistSetting:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentContainer.m", + "coverage": 0.9818181818181818, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:]", + "coverage": 1 + }, + { + "name": "__55+[FIRComponentContainer registerAsComponentRegistrant:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:inSet:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:registrants:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer populateComponentsFromRegisteredClasses:forApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer instantiateInstanceForProtocol:withBlock:]", + "coverage": 0.9285714285714286 + }, + { + "name": "-[FIRComponentContainer instanceForProtocol:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer removeAllCachedInstances]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentType.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentType instanceForProtocol:inContainer:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponent.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponent componentWithProtocol:creationBlock:]", + "coverage": 1 + }, + { + "name": "+[FIRComponent componentWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + }, + { + "name": "-[FIRComponent initWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAppAssociationRegistration.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRAppAssociationRegistration registeredObjectWithHost:key:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "main.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "main", + "coverage": 1 + } + ] + } + ] + }, + { + "name": "Database_Example_iOS.app", + "coverage": 0.6189063408958697, + "files": [ + { + "name": "FTupleStringNode.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FTupleStringNode initWithString:andNode:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRNoopAuthTokenProvider.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FIRNoopAuthTokenProvider fetchTokenForcingRefresh:withCallback:]", + "coverage": 0 + }, + { + "name": "__66-[FIRNoopAuthTokenProvider fetchTokenForcingRefresh:withCallback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRNoopAuthTokenProvider listenForTokenChanges:]", + "coverage": 0 + } + ] + }, + { + "name": "FPendingPut.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FPendingPut initWithPath:andData:andPriority:]", + "coverage": 0 + }, + { + "name": "-[FPendingPut encodeWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FPendingPut initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FPendingPutPriority initWithPath:andPriority:]", + "coverage": 0 + }, + { + "name": "-[FPendingPutPriority encodeWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FPendingPutPriority initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FPendingUpdate initWithPath:andData:]", + "coverage": 0 + }, + { + "name": "-[FPendingUpdate encodeWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FPendingUpdate initWithCoder:]", + "coverage": 0 + } + ] + }, + { + "name": "FListenComplete.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FListenComplete initWithSource:path:]", + "coverage": 0 + }, + { + "name": "-[FListenComplete operationForChild:]", + "coverage": 0 + }, + { + "name": "-[FListenComplete description]", + "coverage": 0 + } + ] + }, + { + "name": "FIRServerValue.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[FIRServerValue timestamp]", + "coverage": 0 + } + ] + }, + { + "name": "FValueEventRegistration.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FValueEventRegistration initWithRepo:handle:callback:cancelCallback:]", + "coverage": 0 + }, + { + "name": "-[FValueEventRegistration responseTo:]", + "coverage": 0 + }, + { + "name": "-[FValueEventRegistration createEventFrom:query:]", + "coverage": 0 + }, + { + "name": "-[FValueEventRegistration fireEvent:queue:]", + "coverage": 0 + }, + { + "name": "__43-[FValueEventRegistration fireEvent:queue:]_block_invoke", + "coverage": 0 + }, + { + "name": "__43-[FValueEventRegistration fireEvent:queue:]_block_invoke.57", + "coverage": 0 + }, + { + "name": "-[FValueEventRegistration createCancelEventFromError:path:]", + "coverage": 0 + }, + { + "name": "-[FValueEventRegistration matches:]", + "coverage": 0 + } + ] + }, + { + "name": "FKeepSyncedEventRegistration.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[FKeepSyncedEventRegistration instance]", + "coverage": 0 + }, + { + "name": "__40+[FKeepSyncedEventRegistration instance]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FKeepSyncedEventRegistration responseTo:]", + "coverage": 0 + }, + { + "name": "-[FKeepSyncedEventRegistration createEventFrom:query:]", + "coverage": 0 + }, + { + "name": "-[FKeepSyncedEventRegistration fireEvent:queue:]", + "coverage": 0 + }, + { + "name": "-[FKeepSyncedEventRegistration createCancelEventFromError:path:]", + "coverage": 0 + }, + { + "name": "-[FKeepSyncedEventRegistration handle]", + "coverage": 0 + }, + { + "name": "-[FKeepSyncedEventRegistration matches:]", + "coverage": 0 + } + ] + }, + { + "name": "FChildEventRegistration.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FChildEventRegistration initWithRepo:handle:callbacks:cancelCallback:]", + "coverage": 0 + }, + { + "name": "-[FChildEventRegistration responseTo:]", + "coverage": 0 + }, + { + "name": "-[FChildEventRegistration createEventFrom:query:]", + "coverage": 0 + }, + { + "name": "-[FChildEventRegistration fireEvent:queue:]", + "coverage": 0 + }, + { + "name": "__43-[FChildEventRegistration fireEvent:queue:]_block_invoke", + "coverage": 0 + }, + { + "name": "__43-[FChildEventRegistration fireEvent:queue:]_block_invoke.68", + "coverage": 0 + }, + { + "name": "-[FChildEventRegistration createCancelEventFromError:path:]", + "coverage": 0 + }, + { + "name": "-[FChildEventRegistration matches:]", + "coverage": 0 + } + ] + }, + { + "name": "FCancelEvent.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FCancelEvent initWithEventRegistration:error:path:]", + "coverage": 0 + }, + { + "name": "-[FCancelEvent fireEventOnQueue:]", + "coverage": 0 + }, + { + "name": "-[FCancelEvent isCancelEvent]", + "coverage": 0 + }, + { + "name": "-[FCancelEvent description]", + "coverage": 0 + } + ] + }, + { + "name": "FEventEmitter.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FEventEmitter initWithAllowedEvents:queue:]", + "coverage": 0 + }, + { + "name": "-[FEventEmitter getInitialEventForType:]", + "coverage": 0 + }, + { + "name": "-[FEventEmitter triggerEventType:data:]", + "coverage": 0 + }, + { + "name": "-[FEventEmitter triggerListener:withData:]", + "coverage": 0 + }, + { + "name": "__42-[FEventEmitter triggerListener:withData:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FEventEmitter observeEventType:withBlock:]", + "coverage": 0 + }, + { + "name": "__44-[FEventEmitter observeEventType:withBlock:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FEventEmitter addEventListener:forEventType:]", + "coverage": 0 + }, + { + "name": "-[FEventEmitter removeObserverForEventType:withHandle:]", + "coverage": 0 + }, + { + "name": "__55-[FEventEmitter removeObserverForEventType:withHandle:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FEventEmitter removeEventListenerWithHandle:forEventType:]", + "coverage": 0 + }, + { + "name": "-[FEventEmitter validateEventType:]", + "coverage": 0 + } + ] + }, + { + "name": "FNextPushId.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[FNextPushId get:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRTransactionResult.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[FIRTransactionResult successWithValue:]", + "coverage": 0 + }, + { + "name": "+[FIRTransactionResult abort]", + "coverage": 0 + } + ] + }, + { + "name": "FTupleNodePath.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FTupleNodePath initWithNode:andPath:]", + "coverage": 0 + } + ] + }, + { + "name": "FTupleObjectNode.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FTupleObjectNode initWithObject:andNode:]", + "coverage": 0 + } + ] + }, + { + "name": "FTupleUserCallback.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FTupleUserCallback initWithHandle:]", + "coverage": 0 + } + ] + }, + { + "name": "FTupleSetIdPath.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FTupleSetIdPath initWithSetId:andPath:]", + "coverage": 0 + } + ] + }, + { + "name": "FTupleTransaction.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FTupleTransaction setAbortStatus:reason:]", + "coverage": 0 + }, + { + "name": "-[FTupleTransaction abortError]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDatabaseQuery.m", + "coverage": 0.05921052631578947, + "type": "objc", + "functions": [ + { + "name": "+[FIRDatabaseQuery sharedQueue]", + "coverage": 1 + }, + { + "name": "__31+[FIRDatabaseQuery sharedQueue]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRDatabaseQuery initWithRepo:path:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery initWithRepo:path:params:orderByCalled:priorityMethodCalled:]", + "coverage": 0.7647058823529411 + }, + { + "name": "-[FIRDatabaseQuery querySpec]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery validateQueryEndpointsForParams:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery validateEqualToCall]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery validateNoPreviousOrderByCalled]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery validateIndexValueType:fromMethod:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryStartingAtValue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryStartingAtValue:childKey:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryStartingAtInternal:childKey:from:priorityMethod:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryEndingAtValue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryEndingAtValue:childKey:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryEndingAtInternal:childKey:from:priorityMethod:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryEqualToValue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryEqualToValue:childKey:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryEqualToInternal:childKey:from:priorityMethod:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery validateLimitRange:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryLimitedToFirst:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryLimitedToLast:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryOrderedByChild:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryOrderedByKey]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryOrderedByValue]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery queryOrderedByPriority]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeEventType:withBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeEventType:andPreviousSiblingKeyWithBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeEventType:withBlock:withCancelBlock:]", + "coverage": 0 + }, + { + "name": "__63-[FIRDatabaseQuery observeEventType:withBlock:withCancelBlock:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeValueEventWithHandle:withBlock:cancelCallback:]", + "coverage": 0 + }, + { + "name": "__73-[FIRDatabaseQuery observeValueEventWithHandle:withBlock:cancelCallback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeChildEventWithHandle:withCallbacks:cancelCallback:]", + "coverage": 0 + }, + { + "name": "__77-[FIRDatabaseQuery observeChildEventWithHandle:withCallbacks:cancelCallback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery removeObserverWithHandle:]", + "coverage": 0 + }, + { + "name": "__45-[FIRDatabaseQuery removeObserverWithHandle:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery removeAllObservers]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery keepSynced:]", + "coverage": 0 + }, + { + "name": "__31-[FIRDatabaseQuery keepSynced:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeSingleEventOfType:withBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeSingleEventOfType:andPreviousSiblingKeyWithBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeSingleEventOfType:withBlock:withCancelBlock:]", + "coverage": 0 + }, + { + "name": "__71-[FIRDatabaseQuery observeSingleEventOfType:withBlock:withCancelBlock:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery observeSingleEventOfType:andPreviousSiblingKeyWithBlock:withCancelBlock:]", + "coverage": 0 + }, + { + "name": "__92-[FIRDatabaseQuery observeSingleEventOfType:andPreviousSiblingKeyWithBlock:withCancelBlock:]_block_invoke", + "coverage": 0 + }, + { + "name": "__92-[FIRDatabaseQuery observeSingleEventOfType:andPreviousSiblingKeyWithBlock:withCancelBlock:]_block_invoke.278", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery description]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseQuery ref]", + "coverage": 0 + } + ] + }, + { + "name": "FTree.m", + "coverage": 0.0625, + "type": "objc", + "functions": [ + { + "name": "-[FTree init]", + "coverage": 1 + }, + { + "name": "-[FTree initWithName:withParent:withNode:]", + "coverage": 0 + }, + { + "name": "-[FTree subTree:]", + "coverage": 0 + }, + { + "name": "-[FTree getValue]", + "coverage": 0 + }, + { + "name": "-[FTree setValue:]", + "coverage": 0 + }, + { + "name": "-[FTree clear]", + "coverage": 0 + }, + { + "name": "-[FTree hasChildren]", + "coverage": 0 + }, + { + "name": "-[FTree isEmpty]", + "coverage": 0 + }, + { + "name": "-[FTree forEachChild:]", + "coverage": 0 + }, + { + "name": "-[FTree forEachChildMutationSafe:]", + "coverage": 0 + }, + { + "name": "-[FTree forEachDescendant:]", + "coverage": 0 + }, + { + "name": "-[FTree forEachDescendant:includeSelf:childrenFirst:]", + "coverage": 0 + }, + { + "name": "__53-[FTree forEachDescendant:includeSelf:childrenFirst:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FTree forEachAncestor:]", + "coverage": 0 + }, + { + "name": "-[FTree forEachAncestor:includeSelf:]", + "coverage": 0 + }, + { + "name": "-[FTree forEachImmediateDescendantWithValue:]", + "coverage": 0 + }, + { + "name": "__45-[FTree forEachImmediateDescendantWithValue:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FTree valueExistsAtOrAbove:]", + "coverage": 0 + }, + { + "name": "-[FTree path]", + "coverage": 0 + }, + { + "name": "-[FTree updateParents]", + "coverage": 0 + }, + { + "name": "-[FTree updateChild:withNode:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDatabaseReference.m", + "coverage": 0.0912280701754386, + "type": "objc", + "functions": [ + { + "name": "-[FIRDatabaseReference initWithConfig:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference initWithRepo:path:]", + "coverage": 1 + }, + { + "name": "-[FIRDatabaseReference key]", + "coverage": 1 + }, + { + "name": "-[FIRDatabaseReference database]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference parent]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference URL]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference description]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference root]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference childByAppendingPath:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference child:]", + "coverage": 1 + }, + { + "name": "-[FIRDatabaseReference childByAutoId]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference setValue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference setValue:withCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference setValue:andPriority:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference setValue:andPriority:withCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference setValueInternal:andPriority:withCompletionBlock:from:]", + "coverage": 0 + }, + { + "name": "__78-[FIRDatabaseReference setValueInternal:andPriority:withCompletionBlock:from:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference removeValue]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference removeValueWithCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference setPriority:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference setPriority:withCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference setPriorityInternal:withCompletionBlock:from:]", + "coverage": 0 + }, + { + "name": "__69-[FIRDatabaseReference setPriorityInternal:withCompletionBlock:from:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference updateChildValues:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference updateChildValues:withCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference updateChildValuesInternal:withCompletionBlock:from:]", + "coverage": 0 + }, + { + "name": "__75-[FIRDatabaseReference updateChildValuesInternal:withCompletionBlock:from:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectSetValue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectSetValue:withCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectSetValue:andPriority:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectSetValue:andPriority:withCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectSetValueInternal:andPriority:withCompletionBlock:from:]", + "coverage": 0 + }, + { + "name": "__90-[FIRDatabaseReference onDisconnectSetValueInternal:andPriority:withCompletionBlock:from:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectRemoveValue]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectRemoveValueWithCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectUpdateChildValues:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectUpdateChildValues:withCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference onDisconnectUpdateChildValuesInternal:withCompletionBlock:from:]", + "coverage": 0 + }, + { + "name": "__87-[FIRDatabaseReference onDisconnectUpdateChildValuesInternal:withCompletionBlock:from:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference cancelDisconnectOperations]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference cancelDisconnectOperationsWithCompletionBlock:]", + "coverage": 0 + }, + { + "name": "__70-[FIRDatabaseReference cancelDisconnectOperationsWithCompletionBlock:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FIRDatabaseReference goOffline]", + "coverage": 0 + }, + { + "name": "+[FIRDatabaseReference goOnline]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference observeEventType:withBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference observeEventType:andPreviousSiblingKeyWithBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference observeEventType:withBlock:withCancelBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference removeObserverWithHandle:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference removeAllObservers]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference keepSynced:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference observeSingleEventOfType:withBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference observeSingleEventOfType:andPreviousSiblingKeyWithBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference observeSingleEventOfType:withBlock:withCancelBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference observeSingleEventOfType:andPreviousSiblingKeyWithBlock:withCancelBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryLimitedToFirst:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryLimitedToLast:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryOrderedByChild:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryOrderedByKey]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryOrderedByPriority]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryStartingAtValue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryStartingAtValue:childKey:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryEndingAtValue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryEndingAtValue:childKey:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryEqualToValue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference queryEqualToValue:childKey:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference runTransactionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference runTransactionBlock:andCompletionBlock:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseReference runTransactionBlock:andCompletionBlock:withLocalEvents:]", + "coverage": 0 + }, + { + "name": "__79-[FIRDatabaseReference runTransactionBlock:andCompletionBlock:withLocalEvents:]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "FRepo.m", + "coverage": 0.14897260273972604, + "type": "objc", + "functions": [ + { + "name": "-[FRepo initWithRepoInfo:config:database:]", + "coverage": 1 + }, + { + "name": "__42-[FRepo initWithRepoInfo:config:database:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FRepo deferredInit]", + "coverage": 0.8089887640449438 + }, + { + "name": "__21-[FRepo deferredInit]_block_invoke", + "coverage": 0 + }, + { + "name": "__21-[FRepo deferredInit]_block_invoke.99", + "coverage": 0 + }, + { + "name": "__21-[FRepo deferredInit]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__21-[FRepo deferredInit]_block_invoke.128", + "coverage": 0 + }, + { + "name": "__21-[FRepo deferredInit]_block_invoke_2.138", + "coverage": 0 + }, + { + "name": "__21-[FRepo deferredInit]_block_invoke_3", + "coverage": 0 + }, + { + "name": "__21-[FRepo deferredInit]_block_invoke.149", + "coverage": 0 + }, + { + "name": "-[FRepo restoreWrites]", + "coverage": 1 + }, + { + "name": "__22-[FRepo restoreWrites]_block_invoke", + "coverage": 0 + }, + { + "name": "__22-[FRepo restoreWrites]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FRepo name]", + "coverage": 0 + }, + { + "name": "-[FRepo description]", + "coverage": 0 + }, + { + "name": "-[FRepo interrupt]", + "coverage": 0 + }, + { + "name": "-[FRepo resume]", + "coverage": 0 + }, + { + "name": "-[FRepo dispose]", + "coverage": 0 + }, + { + "name": "-[FRepo nextWriteId]", + "coverage": 0 + }, + { + "name": "-[FRepo serverTime]", + "coverage": 0 + }, + { + "name": "-[FRepo set:withNode:withCallback:]", + "coverage": 0 + }, + { + "name": "__35-[FRepo set:withNode:withCallback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FRepo update:withNodes:withCallback:]", + "coverage": 0 + }, + { + "name": "__39-[FRepo update:withNodes:withCallback:]_block_invoke", + "coverage": 0 + }, + { + "name": "__39-[FRepo update:withNodes:withCallback:]_block_invoke.278", + "coverage": 0 + }, + { + "name": "-[FRepo onDisconnectCancel:withCallback:]", + "coverage": 0 + }, + { + "name": "__41-[FRepo onDisconnectCancel:withCallback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FRepo onDisconnectSet:withNode:withCallback:]", + "coverage": 0 + }, + { + "name": "__47-[FRepo onDisconnectSet:withNode:withCallback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FRepo onDisconnectUpdate:withNodes:withCallback:]", + "coverage": 0 + }, + { + "name": "__51-[FRepo onDisconnectUpdate:withNodes:withCallback:]_block_invoke", + "coverage": 0 + }, + { + "name": "__51-[FRepo onDisconnectUpdate:withNodes:withCallback:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FRepo purgeOutstandingWrites]", + "coverage": 0 + }, + { + "name": "-[FRepo addEventRegistration:forQuery:]", + "coverage": 0 + }, + { + "name": "-[FRepo removeEventRegistration:forQuery:]", + "coverage": 0 + }, + { + "name": "-[FRepo keepQuery:synced:]", + "coverage": 0 + }, + { + "name": "-[FRepo updateInfo:withValue:]", + "coverage": 0.7692307692307693 + }, + { + "name": "-[FRepo callOnComplete:withStatus:errorReason:andPath:]", + "coverage": 0 + }, + { + "name": "__55-[FRepo callOnComplete:withStatus:errorReason:andPath:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FRepo ackWrite:rerunTransactionsAtPath:status:]", + "coverage": 0 + }, + { + "name": "-[FRepo warnIfWriteFailedAtPath:status:message:]", + "coverage": 0 + }, + { + "name": "-[FRepo onDataUpdate:forPath:message:isMerge:tagId:]", + "coverage": 0 + }, + { + "name": "-[FRepo onRangeMerge:forPath:tagId:]", + "coverage": 0 + }, + { + "name": "-[FRepo onConnect:]", + "coverage": 0 + }, + { + "name": "-[FRepo onDisconnect:]", + "coverage": 1 + }, + { + "name": "-[FRepo onServerInfoUpdate:updates:]", + "coverage": 0 + }, + { + "name": "-[FRepo setupNotifications]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FRepo didEnterBackground]", + "coverage": 0 + }, + { + "name": "__27-[FRepo didEnterBackground]_block_invoke", + "coverage": 0 + }, + { + "name": "__27-[FRepo didEnterBackground]_block_invoke.466", + "coverage": 0 + }, + { + "name": "-[FRepo runOnDisconnectEvents]", + "coverage": 1 + }, + { + "name": "__30-[FRepo runOnDisconnectEvents]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FRepo dumpListens]", + "coverage": 0 + }, + { + "name": "-[FRepo initTransactions]", + "coverage": 1 + }, + { + "name": "-[FRepo startTransactionOnPath:update:onComplete:withLocalEvents:]", + "coverage": 0 + }, + { + "name": "__66-[FRepo startTransactionOnPath:update:onComplete:withLocalEvents:]_block_invoke", + "coverage": 0 + }, + { + "name": "__66-[FRepo startTransactionOnPath:update:onComplete:withLocalEvents:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__66-[FRepo startTransactionOnPath:update:onComplete:withLocalEvents:]_block_invoke.571", + "coverage": 0 + }, + { + "name": "-[FRepo latestStateAtPath:excludeWriteIds:]", + "coverage": 0 + }, + { + "name": "-[FRepo sendAllReadyTransactions]", + "coverage": 0 + }, + { + "name": "-[FRepo sendReadyTransactionsForTree:]", + "coverage": 0 + }, + { + "name": "__38-[FRepo sendReadyTransactionsForTree:]_block_invoke", + "coverage": 0 + }, + { + "name": "__38-[FRepo sendReadyTransactionsForTree:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FRepo sendTransactionQueue:atPath:]", + "coverage": 0 + }, + { + "name": "__37-[FRepo sendTransactionQueue:atPath:]_block_invoke", + "coverage": 0 + }, + { + "name": "__37-[FRepo sendTransactionQueue:atPath:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FRepo rerunTransactionsForPath:]", + "coverage": 0 + }, + { + "name": "-[FRepo rerunTransactionQueue:atPath:]", + "coverage": 0 + }, + { + "name": "__38-[FRepo rerunTransactionQueue:atPath:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FRepo getAncestorTransactionNodeForPath:]", + "coverage": 0 + }, + { + "name": "-[FRepo buildTransactionQueueAtNode:]", + "coverage": 0 + }, + { + "name": "__37-[FRepo buildTransactionQueueAtNode:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FRepo aggregateTransactionQueuesForNode:andQueue:]", + "coverage": 0 + }, + { + "name": "__52-[FRepo aggregateTransactionQueuesForNode:andQueue:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FRepo pruneCompletedTransactionsBelowNode:]", + "coverage": 0 + }, + { + "name": "__45-[FRepo pruneCompletedTransactionsBelowNode:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FRepo abortTransactionsAtPath:error:]", + "coverage": 0 + }, + { + "name": "__39-[FRepo abortTransactionsAtPath:error:]_block_invoke", + "coverage": 0 + }, + { + "name": "__39-[FRepo abortTransactionsAtPath:error:]_block_invoke.715", + "coverage": 0 + }, + { + "name": "-[FRepo abortTransactionsAtNode:error:]", + "coverage": 0 + }, + { + "name": "__39-[FRepo abortTransactionsAtNode:error:]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "FCachePolicy.m", + "coverage": 0.17647058823529413, + "type": "objc", + "functions": [ + { + "name": "-[FLRUCachePolicy initWithMaxSize:]", + "coverage": 0 + }, + { + "name": "-[FLRUCachePolicy shouldPruneCacheWithSize:numberOfTrackedQueries:]", + "coverage": 0 + }, + { + "name": "-[FLRUCachePolicy shouldCheckCacheSize:]", + "coverage": 0 + }, + { + "name": "-[FLRUCachePolicy percentOfQueriesToPruneAtOnce]", + "coverage": 0 + }, + { + "name": "-[FLRUCachePolicy maxNumberOfQueriesToKeep]", + "coverage": 0 + }, + { + "name": "+[FNoCachePolicy noCachePolicy]", + "coverage": 1 + }, + { + "name": "-[FNoCachePolicy shouldPruneCacheWithSize:numberOfTrackedQueries:]", + "coverage": 0 + }, + { + "name": "-[FNoCachePolicy shouldCheckCacheSize:]", + "coverage": 1 + }, + { + "name": "-[FNoCachePolicy percentOfQueriesToPruneAtOnce]", + "coverage": 0 + }, + { + "name": "-[FNoCachePolicy maxNumberOfQueriesToKeep]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDatabase.m", + "coverage": 0.1986754966887417, + "type": "objc", + "functions": [ + { + "name": "+[FIRDatabase database]", + "coverage": 0 + }, + { + "name": "+[FIRDatabase databaseWithURL:]", + "coverage": 0 + }, + { + "name": "+[FIRDatabase databaseForApp:]", + "coverage": 0 + }, + { + "name": "+[FIRDatabase databaseForApp:URL:]", + "coverage": 0 + }, + { + "name": "+[FIRDatabase buildVersion]", + "coverage": 1 + }, + { + "name": "+[FIRDatabase createDatabaseForTests:config:]", + "coverage": 1 + }, + { + "name": "+[FIRDatabase sdkVersion]", + "coverage": 1 + }, + { + "name": "+[FIRDatabase setLoggingEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRDatabase initWithApp:repoInfo:config:]", + "coverage": 1 + }, + { + "name": "-[FIRDatabase reference]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase referenceWithPath:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase referenceFromURL:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase purgeOutstandingWrites]", + "coverage": 0 + }, + { + "name": "__37-[FIRDatabase purgeOutstandingWrites]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabase goOnline]", + "coverage": 0 + }, + { + "name": "__23-[FIRDatabase goOnline]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabase goOffline]", + "coverage": 0 + }, + { + "name": "__24-[FIRDatabase goOffline]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabase setPersistenceEnabled:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase persistenceEnabled]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase setPersistenceCacheSizeBytes:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase persistenceCacheSizeBytes]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase setCallbackQueue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase callbackQueue]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase assertUnfrozen:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabase ensureRepo]", + "coverage": 1 + } + ] + }, + { + "name": "FIRDatabaseComponent.m", + "coverage": 0.2, + "type": "objc", + "functions": [ + { + "name": "-[FIRDatabaseComponent initWithApp:]", + "coverage": 0 + }, + { + "name": "+[FIRDatabaseComponent load]", + "coverage": 1 + }, + { + "name": "+[FIRDatabaseComponent componentsToRegister]", + "coverage": 1 + }, + { + "name": "__44+[FIRDatabaseComponent componentsToRegister]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseComponent appWillBeDeleted:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseComponent databaseForApp:URL:]", + "coverage": 0 + } + ] + }, + { + "name": "FPersistentConnection.m", + "coverage": 0.23169398907103825, + "type": "objc", + "functions": [ + { + "name": "-[FPersistentConnection initWithRepoInfo:dispatchQueue:config:]", + "coverage": 1 + }, + { + "name": "-[FPersistentConnection dealloc]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection open]", + "coverage": 1 + }, + { + "name": "-[FPersistentConnection listen:tagId:hash:onComplete:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection putData:forPath:withHash:withCallback:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection mergeData:forPath:withCallback:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection onDisconnectPutData:forPath:withCallback:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection onDisconnectMergeData:forPath:withCallback:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection onDisconnectCancelPath:withCallback:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection unlisten:tagId:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection refreshAuthToken:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection connected]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection canSendWrites]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection onReady:atTime:sessionID:]", + "coverage": 0 + }, + { + "name": "__50-[FPersistentConnection onReady:atTime:sessionID:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection onDataMessage:withMessage:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection onDisconnect:withReason:]", + "coverage": 0.88 + }, + { + "name": "-[FPersistentConnection onKill:withReason:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection interruptForReason:]", + "coverage": 0.8 + }, + { + "name": "-[FPersistentConnection resumeForReason:]", + "coverage": 1 + }, + { + "name": "-[FPersistentConnection shouldReconnect]", + "coverage": 1 + }, + { + "name": "-[FPersistentConnection isInterruptedForReason:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection tryScheduleReconnect]", + "coverage": 1 + }, + { + "name": "__45-[FPersistentConnection tryScheduleReconnect]_block_invoke", + "coverage": 1 + }, + { + "name": "__45-[FPersistentConnection tryScheduleReconnect]_block_invoke_2", + "coverage": 0.5909090909090909 + }, + { + "name": "-[FPersistentConnection openNetworkConnectionWithToken:]", + "coverage": 1 + }, + { + "name": "reachabilityCallback", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection enteringForeground]", + "coverage": 0 + }, + { + "name": "__43-[FPersistentConnection enteringForeground]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection setupNotifications]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FPersistentConnection sendAuthAndRestoreStateAfterComplete:]", + "coverage": 0 + }, + { + "name": "__62-[FPersistentConnection sendAuthAndRestoreStateAfterComplete:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection sendUnauth]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection onAuthRevokedWithStatus:andReason:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection onListenRevoked:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection sendOnDisconnectAction:forPath:withData:andCallback:]", + "coverage": 0 + }, + { + "name": "__77-[FPersistentConnection sendOnDisconnectAction:forPath:withData:andCallback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection sendPut:]", + "coverage": 0 + }, + { + "name": "__33-[FPersistentConnection sendPut:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection sendUnlisten:queryParams:tagId:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection putInternal:forAction:forPath:withHash:withCallback:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection sendListen:]", + "coverage": 0 + }, + { + "name": "__36-[FPersistentConnection sendListen:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection warnOnListenWarningsForQuery:payload:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection getNextRequestNumber]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection sendAction:body:sensitive:callback:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection cancelSentTransactions]", + "coverage": 0.6666666666666666 + }, + { + "name": "__47-[FPersistentConnection cancelSentTransactions]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection onDataPushWithAction:andBody:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection restoreAuth]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection restoreState]", + "coverage": 0 + }, + { + "name": "__37-[FPersistentConnection restoreState]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection removeListen:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection removeAllListensAtPath:]", + "coverage": 0 + }, + { + "name": "__48-[FPersistentConnection removeAllListensAtPath:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection purgeOutstandingWrites]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection ackPuts]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection handleTimestamp:]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection sendStats:]", + "coverage": 0 + }, + { + "name": "__35-[FPersistentConnection sendStats:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection sendConnectStats]", + "coverage": 0 + }, + { + "name": "-[FPersistentConnection dumpListens]", + "coverage": 0 + } + ] + }, + { + "name": "FConnection.m", + "coverage": 0.2826086956521739, + "type": "objc", + "functions": [ + { + "name": "-[FConnection initWith:andDispatchQueue:lastSessionID:]", + "coverage": 1 + }, + { + "name": "-[FConnection open]", + "coverage": 1 + }, + { + "name": "-[FConnection closeWithReason:]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FConnection close]", + "coverage": 1 + }, + { + "name": "-[FConnection sendRequest:sensitive:]", + "coverage": 0 + }, + { + "name": "-[FConnection sendData:sensitive:]", + "coverage": 0 + }, + { + "name": "-[FConnection onDisconnect:wasEverConnected:]", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FConnection onMessage:withMessage:]", + "coverage": 0 + }, + { + "name": "-[FConnection onDataMessage:]", + "coverage": 0 + }, + { + "name": "-[FConnection onControl:]", + "coverage": 0 + }, + { + "name": "-[FConnection onConnectionShutdownWithReason:]", + "coverage": 0 + }, + { + "name": "-[FConnection onHandshake:]", + "coverage": 0 + }, + { + "name": "-[FConnection onConnection:readyAtTime:sessionID:]", + "coverage": 0 + }, + { + "name": "-[FConnection onReset:]", + "coverage": 0 + } + ] + }, + { + "name": "fbase64.c", + "coverage": 0.2891566265060241, + "type": "objc", + "functions": [ + { + "name": "f_b64_ntop", + "coverage": 0.9411764705882353 + }, + { + "name": "f_b64_pton", + "coverage": 0 + } + ] + }, + { + "name": "FImmutableSortedDictionary.m", + "coverage": 0.3103448275862069, + "type": "objc", + "functions": [ + { + "name": "+[FImmutableSortedDictionary dictionaryWithComparator:]", + "coverage": 1 + }, + { + "name": "+[FImmutableSortedDictionary fromDictionary:withComparator:]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedDictionary insertKey:withValue:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary removeKey:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary get:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary getPredecessorKey:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary isEmpty]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary count]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary minKey]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary maxKey]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary enumerateKeysAndObjectsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary enumerateKeysAndObjectsReverse:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary contains:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary keyEnumerator]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary keyEnumeratorFrom:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary reverseKeyEnumerator]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary reverseKeyEnumeratorFrom:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary isEqual:]", + "coverage": 0.75 + }, + { + "name": "__38-[FImmutableSortedDictionary isEqual:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedDictionary hash]", + "coverage": 0 + }, + { + "name": "__34-[FImmutableSortedDictionary hash]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary description]", + "coverage": 0 + }, + { + "name": "__41-[FImmutableSortedDictionary description]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedDictionary setObject:forKey:]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedDictionary removeObjectForKey:]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedDictionary objectForKey:]", + "coverage": 1 + } + ] + }, + { + "name": "FRepoManager.m", + "coverage": 0.3157894736842105, + "type": "objc", + "functions": [ + { + "name": "+[FRepoManager configs]", + "coverage": 1 + }, + { + "name": "__23+[FRepoManager configs]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FRepoManager getRepo:config:]", + "coverage": 1 + }, + { + "name": "+[FRepoManager createRepo:config:database:]", + "coverage": 0.8571428571428571 + }, + { + "name": "+[FRepoManager interrupt:]", + "coverage": 0 + }, + { + "name": "__26+[FRepoManager interrupt:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FRepoManager interruptAll]", + "coverage": 0 + }, + { + "name": "__28+[FRepoManager interruptAll]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FRepoManager resume:]", + "coverage": 0 + }, + { + "name": "__23+[FRepoManager resume:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FRepoManager resumeAll]", + "coverage": 0 + }, + { + "name": "__25+[FRepoManager resumeAll]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FRepoManager disposeRepos:]", + "coverage": 0 + }, + { + "name": "__29+[FRepoManager disposeRepos:]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "FDataEvent.m", + "coverage": 0.3225806451612903, + "type": "objc", + "functions": [ + { + "name": "-[FDataEvent initWithEventType:eventRegistration:dataSnapshot:]", + "coverage": 0 + }, + { + "name": "-[FDataEvent initWithEventType:eventRegistration:dataSnapshot:prevName:]", + "coverage": 1 + }, + { + "name": "-[FDataEvent path]", + "coverage": 0 + }, + { + "name": "-[FDataEvent fireEventOnQueue:]", + "coverage": 0 + }, + { + "name": "-[FDataEvent isCancelEvent]", + "coverage": 0 + }, + { + "name": "-[FDataEvent description]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAppDelegate.m", + "coverage": 0.3333333333333333, + "type": "objc", + "functions": [ + { + "name": "-[FIRAppDelegate application:didFinishLaunchingWithOptions:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillResignActive:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidEnterBackground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationWillEnterForeground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidBecomeActive:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillTerminate:]", + "coverage": 0 + } + ] + }, + { + "name": "db.h", + "coverage": 0.3333333333333333, + "type": "objc", + "functions": [ + { + "name": "leveldb::Range::Range()", + "coverage": 0 + }, + { + "name": "leveldb::Range::Range(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::DB::DB()", + "coverage": 1 + } + ] + }, + { + "name": "FValidation.m", + "coverage": 0.33852140077821014, + "type": "objc", + "functions": [ + { + "name": "+[FValidation validateFrom:writablePath:]", + "coverage": 0 + }, + { + "name": "+[FValidation validateFrom:knownEventType:]", + "coverage": 0 + }, + { + "name": "+[FValidation isValidPathString:]", + "coverage": 1 + }, + { + "name": "__33+[FValidation isValidPathString:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FValidation validateFrom:validPathString:]", + "coverage": 0.6 + }, + { + "name": "+[FValidation validateFrom:validRootPathString:]", + "coverage": 0.8571428571428571 + }, + { + "name": "__48+[FValidation validateFrom:validRootPathString:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FValidation isValidKey:]", + "coverage": 1 + }, + { + "name": "__26+[FValidation isValidKey:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FValidation validateFrom:validKey:]", + "coverage": 0 + }, + { + "name": "+[FValidation validateFrom:validURL:]", + "coverage": 0 + }, + { + "name": "+[FValidation stringNonempty:]", + "coverage": 0 + }, + { + "name": "+[FValidation validateToken:]", + "coverage": 0 + }, + { + "name": "+[FValidation handleError:withUserCallback:]", + "coverage": 0 + }, + { + "name": "+[FValidation handleError:withSuccessCallback:]", + "coverage": 0 + }, + { + "name": "+[FValidation validateFrom:isValidLeafValue:withPath:]", + "coverage": 0.40816326530612246 + }, + { + "name": "+[FValidation parseAndValidateKey:fromFunction:path:]", + "coverage": 0.4 + }, + { + "name": "+[FValidation validateFrom:validDictionaryKey:withPath:]", + "coverage": 0.4 + }, + { + "name": "+[FValidation validateFrom:validUpdateDictionaryKey:withValue:]", + "coverage": 0 + }, + { + "name": "__63+[FValidation validateFrom:validUpdateDictionaryKey:withValue:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FValidation validateFrom:isValidPriorityValue:withPath:]", + "coverage": 1 + }, + { + "name": "+[FValidation validatePriorityValue:]", + "coverage": 0 + }, + { + "name": "+[FValidation validateFrom:isValidPriorityValue:withPath:throwError:]", + "coverage": 0.23076923076923078 + } + ] + }, + { + "name": "FEventRaiser.m", + "coverage": 0.3448275862068966, + "type": "objc", + "functions": [ + { + "name": "-[FEventRaiser init]", + "coverage": 0 + }, + { + "name": "-[FEventRaiser initWithQueue:]", + "coverage": 1 + }, + { + "name": "-[FEventRaiser raiseEvents:]", + "coverage": 0.6 + }, + { + "name": "-[FEventRaiser raiseCallback:]", + "coverage": 0 + }, + { + "name": "-[FEventRaiser raiseCallbacks:]", + "coverage": 0 + }, + { + "name": "+[FEventRaiser raiseCallbacks:queue:]", + "coverage": 0 + } + ] + }, + { + "name": "FWebSocketConnection.m", + "coverage": 0.35944700460829493, + "type": "objc", + "functions": [ + { + "name": "-[FWebSocketConnection initWith:andQueue:lastSessionID:]", + "coverage": 1 + }, + { + "name": "-[FWebSocketConnection userAgent]", + "coverage": 0.8709677419354839 + }, + { + "name": "-[FWebSocketConnection buffering]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection open]", + "coverage": 1 + }, + { + "name": "__28-[FWebSocketConnection open]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection close]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection start]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection send:]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection nop:]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection handleNewFrameCount:]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection extractFrameCount:]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection appendFrame:]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection handleIncomingFrame:]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection webSocket:didReceiveMessage:]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection webSocketDidOpen:]", + "coverage": 0 + }, + { + "name": "__41-[FWebSocketConnection webSocketDidOpen:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection webSocket:didFailWithError:]", + "coverage": 1 + }, + { + "name": "-[FWebSocketConnection webSocket:didCloseWithCode:reason:wasClean:]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection closeIfNeverConnected]", + "coverage": 0 + }, + { + "name": "-[FWebSocketConnection shutdown]", + "coverage": 1 + }, + { + "name": "-[FWebSocketConnection onClosed]", + "coverage": 0.8 + }, + { + "name": "-[FWebSocketConnection resetKeepAlive]", + "coverage": 0 + } + ] + }, + { + "name": "FSRWebSocket.m", + "coverage": 0.39594843462246776, + "type": "objc", + "functions": [ + { + "name": "newSHA1String", + "coverage": 0 + }, + { + "name": "-[NSData(FSRWebSocket) stringBySHA1ThenBase64Encoding]", + "coverage": 0 + }, + { + "name": "-[NSString(FSRWebSocket) stringBySHA1ThenBase64Encoding]", + "coverage": 0 + }, + { + "name": "+[FSRWebSocket initialize]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket initWithURLRequest:protocols:queue:andUserAgent:]", + "coverage": 0.8928571428571429 + }, + { + "name": "-[FSRWebSocket initWithURLRequest:protocols:]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket initWithURLRequest:queue:andUserAgent:]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket initWithURLRequest:]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket initWithURL:]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket initWithURL:protocols:]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _SR_commonInit]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket assertOnWorkQueue]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket dealloc]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket setReadyState:]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket open]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _performDelegateBlock:]", + "coverage": 0.875 + }, + { + "name": "-[FSRWebSocket setDelegateDispatchQueue:]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _checkHandshake:]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _HTTPHeadersDidFinish]", + "coverage": 0.23684210526315788 + }, + { + "name": "__37-[FSRWebSocket _HTTPHeadersDidFinish]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _readHTTPHeader]", + "coverage": 1 + }, + { + "name": "__31-[FSRWebSocket _readHTTPHeader]_block_invoke", + "coverage": 0.8 + }, + { + "name": "-[FSRWebSocket didConnect]", + "coverage": 0.9512195121951219 + }, + { + "name": "__26-[FSRWebSocket didConnect]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _initializeStreams]", + "coverage": 0.6818181818181818 + }, + { + "name": "-[FSRWebSocket _connect]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket scheduleInRunLoop:forMode:]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket unscheduleFromRunLoop:forMode:]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket close]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket closeWithCode:reason:]", + "coverage": 0 + }, + { + "name": "__37-[FSRWebSocket closeWithCode:reason:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _closeWithProtocolError:]", + "coverage": 0 + }, + { + "name": "__40-[FSRWebSocket _closeWithProtocolError:]_block_invoke", + "coverage": 0 + }, + { + "name": "__40-[FSRWebSocket _closeWithProtocolError:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _failWithError:]", + "coverage": 1 + }, + { + "name": "__31-[FSRWebSocket _failWithError:]_block_invoke", + "coverage": 1 + }, + { + "name": "__31-[FSRWebSocket _failWithError:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _writeData:]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FSRWebSocket send:]", + "coverage": 0 + }, + { + "name": "__21-[FSRWebSocket send:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket handlePing:]", + "coverage": 0 + }, + { + "name": "__27-[FSRWebSocket handlePing:]_block_invoke", + "coverage": 0 + }, + { + "name": "__27-[FSRWebSocket handlePing:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket handlePong]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _handleMessage:]", + "coverage": 0 + }, + { + "name": "__31-[FSRWebSocket _handleMessage:]_block_invoke", + "coverage": 0 + }, + { + "name": "closeCodeIsValid", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket handleCloseWithData:]", + "coverage": 0 + }, + { + "name": "__36-[FSRWebSocket handleCloseWithData:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _disconnect]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _handleFrameWithData:opCode:]", + "coverage": 0 + }, + { + "name": "__44-[FSRWebSocket _handleFrameWithData:opCode:]_block_invoke", + "coverage": 0 + }, + { + "name": "__44-[FSRWebSocket _handleFrameWithData:opCode:]_block_invoke.320", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _handleFrameHeader:curData:]", + "coverage": 0 + }, + { + "name": "__43-[FSRWebSocket _handleFrameHeader:curData:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _readFrameContinue]", + "coverage": 0 + }, + { + "name": "__34-[FSRWebSocket _readFrameContinue]_block_invoke", + "coverage": 0 + }, + { + "name": "__34-[FSRWebSocket _readFrameContinue]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _readFrameNew]", + "coverage": 0 + }, + { + "name": "__29-[FSRWebSocket _readFrameNew]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _pumpWriting]", + "coverage": 0.7209302325581395 + }, + { + "name": "__28-[FSRWebSocket _pumpWriting]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _addConsumerWithScanner:callback:]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _addConsumerWithDataLength:callback:readToCurrentFrame:unmaskBytes:]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _addConsumerWithScanner:callback:dataLength:]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _scheduleCleanup]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _cleanupSelfReference:]", + "coverage": 1 + }, + { + "name": "__38-[FSRWebSocket _cleanupSelfReference:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _readUntilHeaderCompleteWithCallback:]", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _readUntilBytes:length:callback:]", + "coverage": 1 + }, + { + "name": "__48-[FSRWebSocket _readUntilBytes:length:callback:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket _innerPumpScanner]", + "coverage": 0.4368932038834951 + }, + { + "name": "__33-[FSRWebSocket _innerPumpScanner]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket _pumpScanner]", + "coverage": 0.8666666666666667 + }, + { + "name": "-[FSRWebSocket _sendFrameWithOpcode:data:]", + "coverage": 0 + }, + { + "name": "-[FSRWebSocket stream:handleEvent:]", + "coverage": 0.4482758620689655 + }, + { + "name": "__35-[FSRWebSocket stream:handleEvent:]_block_invoke", + "coverage": 0 + }, + { + "name": "__35-[FSRWebSocket stream:handleEvent:]_block_invoke.457", + "coverage": 1 + }, + { + "name": "-[FSRWebSocket safeHandleEvent:stream:]", + "coverage": 0.5955056179775281 + }, + { + "name": "__39-[FSRWebSocket safeHandleEvent:stream:]_block_invoke", + "coverage": 0 + }, + { + "name": "__39-[FSRWebSocket safeHandleEvent:stream:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FSRIOConsumer setupWithScanner:handler:bytesNeeded:readToCurrentFrame:unmaskBytes:]", + "coverage": 1 + }, + { + "name": "-[FSRIOConsumerPool initWithBufferCapacity:]", + "coverage": 1 + }, + { + "name": "-[FSRIOConsumerPool init]", + "coverage": 1 + }, + { + "name": "-[FSRIOConsumerPool consumerWithScanner:handler:bytesNeeded:readToCurrentFrame:unmaskBytes:]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[FSRIOConsumerPool returnConsumer:]", + "coverage": 0 + }, + { + "name": "-[NSURLRequest(FCertificateAdditions) FSR_SSLPinnedCertificates]", + "coverage": 0 + }, + { + "name": "-[NSMutableURLRequest(FCertificateAdditions) FSR_SSLPinnedCertificates]", + "coverage": 0 + }, + { + "name": "-[NSMutableURLRequest(FCertificateAdditions) setFSR_SSLPinnedCertificates:]", + "coverage": 0 + }, + { + "name": "-[NSURL(FSRWebSocket) SR_origin]", + "coverage": 0.8666666666666667 + }, + { + "name": "SRFastLog", + "coverage": 1 + }, + { + "name": "validate_dispatch_data_partial_string", + "coverage": 0 + }, + { + "name": "+[NSRunLoop(FSRWebSocket) FSR_networkRunLoop]", + "coverage": 1 + }, + { + "name": "__45+[NSRunLoop(FSRWebSocket) FSR_networkRunLoop]_block_invoke", + "coverage": 1 + }, + { + "name": "-[_FSRRunLoopThread dealloc]", + "coverage": 0 + }, + { + "name": "-[_FSRRunLoopThread init]", + "coverage": 1 + }, + { + "name": "-[_FSRRunLoopThread main]", + "coverage": 1 + }, + { + "name": "-[_FSRRunLoopThread runLoop]", + "coverage": 1 + } + ] + }, + { + "name": "FStringUtilities.m", + "coverage": 0.41935483870967744, + "type": "objc", + "functions": [ + { + "name": "+[FStringUtilities base64EncodedSha1:]", + "coverage": 1 + }, + { + "name": "+[FStringUtilities urlDecoded:]", + "coverage": 0 + }, + { + "name": "+[FStringUtilities urlEncoded:]", + "coverage": 0 + }, + { + "name": "+[FStringUtilities sanitizedForUserAgent:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRViewController.m", + "coverage": 0.5, + "type": "objc", + "functions": [ + { + "name": "-[FIRViewController viewDidLoad]", + "coverage": 1 + }, + { + "name": "-[FIRViewController didReceiveMemoryWarning]", + "coverage": 0 + } + ] + }, + { + "name": "FValueIndex.m", + "coverage": 0.5166666666666667, + "type": "objc", + "functions": [ + { + "name": "-[FValueIndex compareKey:andNode:toOtherKey:andNode:]", + "coverage": 0.875 + }, + { + "name": "-[FValueIndex compareKey:andNode:toOtherKey:andNode:reverse:]", + "coverage": 0 + }, + { + "name": "-[FValueIndex compareNamedNode:toNamedNode:]", + "coverage": 1 + }, + { + "name": "-[FValueIndex isDefinedOn:]", + "coverage": 0 + }, + { + "name": "-[FValueIndex indexedValueChangedBetween:and:]", + "coverage": 0 + }, + { + "name": "-[FValueIndex minPost]", + "coverage": 0 + }, + { + "name": "-[FValueIndex maxPost]", + "coverage": 0 + }, + { + "name": "-[FValueIndex makePost:name:]", + "coverage": 0 + }, + { + "name": "-[FValueIndex queryDefinition]", + "coverage": 1 + }, + { + "name": "-[FValueIndex description]", + "coverage": 0 + }, + { + "name": "-[FValueIndex copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[FValueIndex isEqual:]", + "coverage": 1 + }, + { + "name": "-[FValueIndex hash]", + "coverage": 1 + }, + { + "name": "+[FValueIndex valueIndex]", + "coverage": 1 + }, + { + "name": "__25+[FValueIndex valueIndex]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FImmutableSortedSet.m", + "coverage": 0.5421686746987951, + "type": "objc", + "functions": [ + { + "name": "+[FImmutableSortedSet setWithKeysFromDictionary:comparator:]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedSet initWithDictionary:]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedSet contains:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedSet addObject:]", + "coverage": 0.75 + }, + { + "name": "-[FImmutableSortedSet removeObject:]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedSet containsObject:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedSet firstObject]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedSet lastObject]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedSet predecessorEntry:]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedSet count]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedSet isEmpty]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedSet enumerateObjectsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[FImmutableSortedSet enumerateObjectsReverse:usingBlock:]", + "coverage": 1 + }, + { + "name": "__58-[FImmutableSortedSet enumerateObjectsReverse:usingBlock:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedSet objectEnumerator]", + "coverage": 1 + }, + { + "name": "-[FImmutableSortedSet description]", + "coverage": 0 + }, + { + "name": "__34-[FImmutableSortedSet description]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "FWriteRecord.m", + "coverage": 0.5526315789473685, + "type": "objc", + "functions": [ + { + "name": "-[FWriteRecord initWithPath:overwrite:writeId:visible:]", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FWriteRecord initWithPath:merge:writeId:]", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FWriteRecord overwrite]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FWriteRecord compoundWrite]", + "coverage": 0 + }, + { + "name": "-[FWriteRecord isMerge]", + "coverage": 0 + }, + { + "name": "-[FWriteRecord isOverwrite]", + "coverage": 1 + }, + { + "name": "-[FWriteRecord description]", + "coverage": 0 + }, + { + "name": "-[FWriteRecord isEqual:]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[FWriteRecord hash]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDatabaseConfig.m", + "coverage": 0.6, + "type": "objc", + "functions": [ + { + "name": "-[FIRDatabaseConfig init]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseConfig initWithSessionIdentifier:authTokenProvider:]", + "coverage": 1 + }, + { + "name": "-[FIRDatabaseConfig assertUnfrozen]", + "coverage": 0.6 + }, + { + "name": "-[FIRDatabaseConfig setAuthTokenProvider:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseConfig setPersistenceEnabled:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseConfig setPersistenceCacheSizeBytes:]", + "coverage": 1 + }, + { + "name": "-[FIRDatabaseConfig setCallbackQueue:]", + "coverage": 0 + }, + { + "name": "-[FIRDatabaseConfig freeze]", + "coverage": 1 + } + ] + }, + { + "name": "FPersistenceManager.m", + "coverage": 0.6131386861313869, + "type": "objc", + "functions": [ + { + "name": "-[FPersistenceManager initWithStorageEngine:cachePolicy:]", + "coverage": 1 + }, + { + "name": "-[FPersistenceManager close]", + "coverage": 0 + }, + { + "name": "-[FPersistenceManager saveUserOverwrite:atPath:writeId:]", + "coverage": 0 + }, + { + "name": "-[FPersistenceManager saveUserMerge:atPath:writeId:]", + "coverage": 0 + }, + { + "name": "-[FPersistenceManager removeUserWrite:]", + "coverage": 0 + }, + { + "name": "-[FPersistenceManager removeAllUserWrites]", + "coverage": 0 + }, + { + "name": "-[FPersistenceManager userWrites]", + "coverage": 0 + }, + { + "name": "-[FPersistenceManager serverCacheForQuery:]", + "coverage": 0.9629629629629629 + }, + { + "name": "-[FPersistenceManager updateServerCacheWithNode:forQuery:]", + "coverage": 1 + }, + { + "name": "-[FPersistenceManager updateServerCacheWithMerge:atPath:]", + "coverage": 0 + }, + { + "name": "-[FPersistenceManager applyUserMerge:toServerCacheAtPath:]", + "coverage": 1 + }, + { + "name": "__58-[FPersistenceManager applyUserMerge:toServerCacheAtPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FPersistenceManager applyUserWrite:toServerCacheAtPath:]", + "coverage": 1 + }, + { + "name": "-[FPersistenceManager setQueryComplete:]", + "coverage": 1 + }, + { + "name": "-[FPersistenceManager setQueryActive:]", + "coverage": 1 + }, + { + "name": "-[FPersistenceManager setQueryInactive:]", + "coverage": 0 + }, + { + "name": "-[FPersistenceManager doPruneCheckAfterServerUpdate]", + "coverage": 0.17391304347826086 + }, + { + "name": "-[FPersistenceManager setTrackedQueryKeys:forQuery:]", + "coverage": 1 + }, + { + "name": "-[FPersistenceManager updateTrackedQueryKeysWithAddedKeys:removedKeys:forQuery:]", + "coverage": 0 + } + ] + }, + { + "name": "FMaxNode.m", + "coverage": 0.6176470588235294, + "type": "objc", + "functions": [ + { + "name": "-[FMaxNode init]", + "coverage": 1 + }, + { + "name": "+[FMaxNode maxNode]", + "coverage": 1 + }, + { + "name": "__19+[FMaxNode maxNode]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FMaxNode compare:]", + "coverage": 0 + }, + { + "name": "-[FMaxNode isEqual:]", + "coverage": 0 + }, + { + "name": "-[FMaxNode getImmediateChild:]", + "coverage": 0 + }, + { + "name": "-[FMaxNode isEmpty]", + "coverage": 1 + } + ] + }, + { + "name": "status.h", + "coverage": 0.631578947368421, + "type": "objc", + "functions": [ + { + "name": "leveldb::Status::Status()", + "coverage": 1 + }, + { + "name": "leveldb::Status::~Status()", + "coverage": 1 + }, + { + "name": "leveldb::Status::OK()", + "coverage": 1 + }, + { + "name": "leveldb::Status::NotFound(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::Status::Corruption(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::Status::NotSupported(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::Status::InvalidArgument(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::Status::IOError(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::Status::ok() const", + "coverage": 1 + }, + { + "name": "leveldb::Status::IsNotFound() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::IsCorruption() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::IsIOError() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::IsNotSupportedError() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::IsInvalidArgument() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::code() const", + "coverage": 1 + }, + { + "name": "leveldb::Status::Status(leveldb::Status const&)", + "coverage": 1 + }, + { + "name": "leveldb::Status::operator=(leveldb::Status const&)", + "coverage": 1 + } + ] + }, + { + "name": "APLevelDB.mm", + "coverage": 0.6482084690553745, + "type": "cpp", + "functions": [ + { + "name": "+[APLevelDB levelDBWithPath:error:]", + "coverage": 1 + }, + { + "name": "-[APLevelDB initWithPath:error:]", + "coverage": 1 + }, + { + "name": "-[APLevelDB close]", + "coverage": 1 + }, + { + "name": "-[APLevelDB dealloc]", + "coverage": 1 + }, + { + "name": "+[APLevelDB defaultCreateOptions]", + "coverage": 1 + }, + { + "name": "-[APLevelDB setData:forKey:]", + "coverage": 1 + }, + { + "name": "-[APLevelDB setString:forKey:]", + "coverage": 0 + }, + { + "name": "-[APLevelDB dataForKey:]", + "coverage": 0 + }, + { + "name": "-[APLevelDB stringForKey:]", + "coverage": 0 + }, + { + "name": "-[APLevelDB removeKey:]", + "coverage": 1 + }, + { + "name": "-[APLevelDB allKeys]", + "coverage": 0 + }, + { + "name": "__20-[APLevelDB allKeys]_block_invoke", + "coverage": 0 + }, + { + "name": "-[APLevelDB enumerateKeysAndValuesAsStrings:]", + "coverage": 0 + }, + { + "name": "-[APLevelDB enumerateKeysWithPrefix:asStrings:]", + "coverage": 0.9523809523809523 + }, + { + "name": "-[APLevelDB enumerateKeys:]", + "coverage": 1 + }, + { + "name": "-[APLevelDB enumerateKeysWithPrefix:usingBlock:]", + "coverage": 0.95 + }, + { + "name": "-[APLevelDB enumerateKeysAndValuesAsData:]", + "coverage": 1 + }, + { + "name": "-[APLevelDB enumerateKeysWithPrefix:asData:]", + "coverage": 0.9523809523809523 + }, + { + "name": "-[APLevelDB exactSizeFrom:to:]", + "coverage": 0 + }, + { + "name": "-[APLevelDB approximateSizeFrom:to:]", + "coverage": 0 + }, + { + "name": "-[APLevelDB objectForKeyedSubscript:]", + "coverage": 0 + }, + { + "name": "-[APLevelDB setObject:forKeyedSubscript:]", + "coverage": 0 + }, + { + "name": "-[APLevelDB beginWriteBatch]", + "coverage": 1 + }, + { + "name": "-[APLevelDB commitWriteBatch:]", + "coverage": 0.9 + }, + { + "name": "+[APLevelDBIterator iteratorWithLevelDB:]", + "coverage": 1 + }, + { + "name": "-[APLevelDBIterator initWithLevelDB:]", + "coverage": 1 + }, + { + "name": "-[APLevelDBIterator init]", + "coverage": 0 + }, + { + "name": "-[APLevelDBIterator dealloc]", + "coverage": 1 + }, + { + "name": "-[APLevelDBIterator seekToKey:]", + "coverage": 1 + }, + { + "name": "-[APLevelDBIterator seekToFirst]", + "coverage": 0 + }, + { + "name": "-[APLevelDBIterator seekToLast]", + "coverage": 0 + }, + { + "name": "-[APLevelDBIterator nextKey]", + "coverage": 1 + }, + { + "name": "-[APLevelDBIterator key]", + "coverage": 1 + }, + { + "name": "-[APLevelDBIterator valueAsString]", + "coverage": 0 + }, + { + "name": "-[APLevelDBIterator valueAsData]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[APLevelDBWriteBatch initWithLevelDB:]", + "coverage": 1 + }, + { + "name": "-[APLevelDBWriteBatch setData:forKey:]", + "coverage": 1 + }, + { + "name": "-[APLevelDBWriteBatch setString:forKey:]", + "coverage": 1 + }, + { + "name": "-[APLevelDBWriteBatch removeKey:]", + "coverage": 1 + }, + { + "name": "-[APLevelDBWriteBatch clear]", + "coverage": 0 + }, + { + "name": "-[APLevelDBWriteBatch commit]", + "coverage": 1 + } + ] + }, + { + "name": "FSnapshotUtilities.m", + "coverage": 0.6577946768060836, + "type": "objc", + "functions": [ + { + "name": "+[FSnapshotUtilities nodeFrom:]", + "coverage": 1 + }, + { + "name": "+[FSnapshotUtilities nodeFrom:priority:]", + "coverage": 1 + }, + { + "name": "+[FSnapshotUtilities nodeFrom:withValidationFrom:]", + "coverage": 1 + }, + { + "name": "+[FSnapshotUtilities nodeFrom:priority:withValidationFrom:]", + "coverage": 1 + }, + { + "name": "+[FSnapshotUtilities nodeFrom:priority:withValidationFrom:atDepth:path:]", + "coverage": 1 + }, + { + "name": "+[FSnapshotUtilities internalNodeFrom:priority:withValidationFrom:atDepth:path:]", + "coverage": 0.6052631578947368 + }, + { + "name": "+[FSnapshotUtilities compoundWriteFromDictionary:withValidationFrom:]", + "coverage": 0 + }, + { + "name": "__69+[FSnapshotUtilities compoundWriteFromDictionary:withValidationFrom:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FSnapshotUtilities validatePriorityNode:]", + "coverage": 0.8888888888888888 + }, + { + "name": "+[FSnapshotUtilities appendHashRepresentationForLeafNode:toString:hashVersion:]", + "coverage": 0.9354838709677419 + }, + { + "name": "+[FSnapshotUtilities appendHashV2RepresentationForString:toString:]", + "coverage": 1 + }, + { + "name": "+[FSnapshotUtilities estimateLeafNodeSize:]", + "coverage": 0.6086956521739131 + }, + { + "name": "+[FSnapshotUtilities estimateSerializedNodeSize:]", + "coverage": 1 + }, + { + "name": "__49+[FSnapshotUtilities estimateSerializedNodeSize:]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FKeyIndex.m", + "coverage": 0.6984126984126984, + "type": "objc", + "functions": [ + { + "name": "-[FKeyIndex init]", + "coverage": 1 + }, + { + "name": "-[FKeyIndex compareKey:andNode:toOtherKey:andNode:]", + "coverage": 1 + }, + { + "name": "-[FKeyIndex compareKey:andNode:toOtherKey:andNode:reverse:]", + "coverage": 0 + }, + { + "name": "-[FKeyIndex compareNamedNode:toNamedNode:]", + "coverage": 0 + }, + { + "name": "-[FKeyIndex isDefinedOn:]", + "coverage": 1 + }, + { + "name": "-[FKeyIndex indexedValueChangedBetween:and:]", + "coverage": 1 + }, + { + "name": "-[FKeyIndex minPost]", + "coverage": 0 + }, + { + "name": "-[FKeyIndex makePost:name:]", + "coverage": 1 + }, + { + "name": "-[FKeyIndex queryDefinition]", + "coverage": 1 + }, + { + "name": "-[FKeyIndex description]", + "coverage": 0 + }, + { + "name": "-[FKeyIndex copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[FKeyIndex isEqual:]", + "coverage": 1 + }, + { + "name": "-[FKeyIndex hash]", + "coverage": 1 + }, + { + "name": "+[FKeyIndex keyIndex]", + "coverage": 1 + }, + { + "name": "__21+[FKeyIndex keyIndex]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FChildChangeAccumulator.m", + "coverage": 0.7, + "type": "objc", + "functions": [ + { + "name": "-[FChildChangeAccumulator init]", + "coverage": 1 + }, + { + "name": "-[FChildChangeAccumulator trackChildChange:]", + "coverage": 0.625 + }, + { + "name": "-[FChildChangeAccumulator changes]", + "coverage": 1 + } + ] + }, + { + "name": "FNamedNode.m", + "coverage": 0.7068965517241379, + "type": "objc", + "functions": [ + { + "name": "+[FNamedNode nodeWithName:node:]", + "coverage": 1 + }, + { + "name": "-[FNamedNode initWithName:andNode:]", + "coverage": 1 + }, + { + "name": "-[FNamedNode copy]", + "coverage": 0 + }, + { + "name": "-[FNamedNode copyWithZone:]", + "coverage": 1 + }, + { + "name": "+[FNamedNode min]", + "coverage": 1 + }, + { + "name": "__17+[FNamedNode min]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FNamedNode max]", + "coverage": 0 + }, + { + "name": "__17+[FNamedNode max]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FNamedNode description]", + "coverage": 0 + }, + { + "name": "-[FNamedNode isEqual:]", + "coverage": 1 + }, + { + "name": "-[FNamedNode hash]", + "coverage": 1 + } + ] + }, + { + "name": "FUtilities.m", + "coverage": 0.7151515151515152, + "type": "objc", + "functions": [ + { + "name": "FFIsLoggingEnabled", + "coverage": 1 + }, + { + "name": "firebaseJobsTroll", + "coverage": 0 + }, + { + "name": "-[FUtilities init]", + "coverage": 1 + }, + { + "name": "+[FUtilities setLoggingEnabled:]", + "coverage": 1 + }, + { + "name": "+[FUtilities getLoggingEnabled]", + "coverage": 0 + }, + { + "name": "+[FUtilities singleton]", + "coverage": 1 + }, + { + "name": "__23+[FUtilities singleton]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FUtilities splitString:intoMaxSize:]", + "coverage": 0 + }, + { + "name": "+[FUtilities LUIDGenerator]", + "coverage": 1 + }, + { + "name": "+[FUtilities decodePath:]", + "coverage": 0.8 + }, + { + "name": "+[FUtilities parseUrl:]", + "coverage": 0.8727272727272727 + }, + { + "name": "+[FUtilities getJavascriptType:]", + "coverage": 0.7727272727272727 + }, + { + "name": "+[FUtilities errorForStatus:andReason:]", + "coverage": 0 + }, + { + "name": "__39+[FUtilities errorForStatus:andReason:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FUtilities intForString:]", + "coverage": 1 + }, + { + "name": "+[FUtilities ieee754StringForNumber:]", + "coverage": 1 + }, + { + "name": "tryParseStringToInt", + "coverage": 1 + }, + { + "name": "+[FUtilities maxName]", + "coverage": 1 + }, + { + "name": "__21+[FUtilities maxName]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FUtilities minName]", + "coverage": 1 + }, + { + "name": "__21+[FUtilities minName]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FUtilities compareKey:toKey:]", + "coverage": 0.9705882352941176 + }, + { + "name": "+[FUtilities keyComparator]", + "coverage": 1 + }, + { + "name": "__27+[FUtilities keyComparator]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FUtilities stringComparator]", + "coverage": 1 + }, + { + "name": "__30+[FUtilities stringComparator]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FUtilities randomDouble]", + "coverage": 1 + } + ] + }, + { + "name": "FAuthTokenProvider.m", + "coverage": 0.734375, + "type": "objc", + "functions": [ + { + "name": "-[FAuthStateListenerWrapper initWithListener:auth:]", + "coverage": 1 + }, + { + "name": "-[FAuthStateListenerWrapper authStateDidChangeNotification:]", + "coverage": 0 + }, + { + "name": "__60-[FAuthStateListenerWrapper authStateDidChangeNotification:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FAuthStateListenerWrapper dealloc]", + "coverage": 0 + }, + { + "name": "-[FIRFirebaseAuthTokenProvider initWithAuth:]", + "coverage": 1 + }, + { + "name": "-[FIRFirebaseAuthTokenProvider fetchTokenForcingRefresh:withCallback:]", + "coverage": 0.8461538461538461 + }, + { + "name": "__70-[FIRFirebaseAuthTokenProvider fetchTokenForcingRefresh:withCallback:]_block_invoke", + "coverage": 1 + }, + { + "name": "__70-[FIRFirebaseAuthTokenProvider fetchTokenForcingRefresh:withCallback:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRFirebaseAuthTokenProvider listenForTokenChanges:]", + "coverage": 1 + }, + { + "name": "+[FAuthTokenProvider authTokenProviderWithAuth:]", + "coverage": 1 + } + ] + }, + { + "name": "FServerValues.m", + "coverage": 0.7435897435897436, + "type": "objc", + "functions": [ + { + "name": "+[FServerValues generateServerValues:]", + "coverage": 1 + }, + { + "name": "+[FServerValues resolveDeferredValue:withServerValues:]", + "coverage": 0.2857142857142857 + }, + { + "name": "+[FServerValues resolveDeferredValueCompoundWrite:withServerValues:]", + "coverage": 1 + }, + { + "name": "__68+[FServerValues resolveDeferredValueCompoundWrite:withServerValues:]_block_invoke", + "coverage": 0.7142857142857143 + }, + { + "name": "+[FServerValues resolveDeferredValueTree:withServerValues:]", + "coverage": 1 + }, + { + "name": "__59+[FServerValues resolveDeferredValueTree:withServerValues:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FServerValues resolveDeferredValueSnapshot:withServerValues:]", + "coverage": 0.8846153846153846 + }, + { + "name": "__63+[FServerValues resolveDeferredValueSnapshot:withServerValues:]_block_invoke", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "FLLRBEmptyNode.m", + "coverage": 0.75, + "type": "objc", + "functions": [ + { + "name": "-[FLLRBEmptyNode description]", + "coverage": 0 + }, + { + "name": "+[FLLRBEmptyNode emptyNode]", + "coverage": 1 + }, + { + "name": "__27+[FLLRBEmptyNode emptyNode]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLLRBEmptyNode copyWith:withValue:withColor:withLeft:withRight:]", + "coverage": 1 + }, + { + "name": "-[FLLRBEmptyNode insertKey:forValue:withComparator:]", + "coverage": 1 + }, + { + "name": "-[FLLRBEmptyNode remove:withComparator:]", + "coverage": 1 + }, + { + "name": "-[FLLRBEmptyNode count]", + "coverage": 1 + }, + { + "name": "-[FLLRBEmptyNode isEmpty]", + "coverage": 1 + }, + { + "name": "-[FLLRBEmptyNode inorderTraversal:]", + "coverage": 1 + }, + { + "name": "-[FLLRBEmptyNode reverseTraversal:]", + "coverage": 1 + }, + { + "name": "-[FLLRBEmptyNode min]", + "coverage": 0 + }, + { + "name": "-[FLLRBEmptyNode minKey]", + "coverage": 0 + }, + { + "name": "-[FLLRBEmptyNode maxKey]", + "coverage": 0 + }, + { + "name": "-[FLLRBEmptyNode isRed]", + "coverage": 1 + }, + { + "name": "-[FLLRBEmptyNode check]", + "coverage": 1 + } + ] + }, + { + "name": "FSyncTree.m", + "coverage": 0.7587719298245614, + "type": "objc", + "functions": [ + { + "name": "-[FListenContainer initWithView:onComplete:]", + "coverage": 1 + }, + { + "name": "-[FListenContainer serverCache]", + "coverage": 0 + }, + { + "name": "-[FListenContainer compoundHash]", + "coverage": 0 + }, + { + "name": "-[FListenContainer simpleHash]", + "coverage": 0 + }, + { + "name": "-[FListenContainer includeCompoundHash]", + "coverage": 0 + }, + { + "name": "-[FSyncTree initWithListenProvider:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree initWithPersistenceManager:listenProvider:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree applyUserOverwriteAtPath:newData:writeId:isVisible:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree applyUserMergeAtPath:changedChildren:writeId:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree ackUserWriteWithWriteId:revert:persist:clock:]", + "coverage": 1 + }, + { + "name": "__58-[FSyncTree ackUserWriteWithWriteId:revert:persist:clock:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSyncTree applyServerOverwriteAtPath:newData:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree applyServerMergeAtPath:changedChildren:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree applyServerRangeMergeAtPath:updates:]", + "coverage": 0 + }, + { + "name": "-[FSyncTree applyListenCompleteAtPath:]", + "coverage": 0 + }, + { + "name": "-[FSyncTree applyTaggedListenCompleteAtPath:tagId:]", + "coverage": 0 + }, + { + "name": "-[FSyncTree applyTaggedOperation:atPath:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree applyTaggedQueryOverwriteAtPath:newData:tagId:]", + "coverage": 0.7857142857142857 + }, + { + "name": "-[FSyncTree applyTaggedQueryMergeAtPath:changedChildren:tagId:]", + "coverage": 0.7857142857142857 + }, + { + "name": "-[FSyncTree applyTaggedServerRangeMergeAtPath:updates:tagId:]", + "coverage": 0 + }, + { + "name": "-[FSyncTree addEventRegistration:forQuery:]", + "coverage": 0.9791666666666666 + }, + { + "name": "__43-[FSyncTree addEventRegistration:forQuery:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSyncTree serverCacheForQuery:]", + "coverage": 0.975 + }, + { + "name": "__33-[FSyncTree serverCacheForQuery:]_block_invoke", + "coverage": 1 + }, + { + "name": "__33-[FSyncTree serverCacheForQuery:]_block_invoke.292", + "coverage": 1 + }, + { + "name": "__33-[FSyncTree serverCacheForQuery:]_block_invoke.303", + "coverage": 0 + }, + { + "name": "-[FSyncTree removeEventRegistration:forQuery:cancelError:]", + "coverage": 0.8831168831168831 + }, + { + "name": "__58-[FSyncTree removeEventRegistration:forQuery:cancelError:]_block_invoke", + "coverage": 1 + }, + { + "name": "__58-[FSyncTree removeEventRegistration:forQuery:cancelError:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__58-[FSyncTree removeEventRegistration:forQuery:cancelError:]_block_invoke.334", + "coverage": 0 + }, + { + "name": "__58-[FSyncTree removeEventRegistration:forQuery:cancelError:]_block_invoke_2.344", + "coverage": 1 + }, + { + "name": "__58-[FSyncTree removeEventRegistration:forQuery:cancelError:]_block_invoke.366", + "coverage": 0 + }, + { + "name": "-[FSyncTree keepQuery:synced:]", + "coverage": 0 + }, + { + "name": "-[FSyncTree removeAllWrites]", + "coverage": 0 + }, + { + "name": "-[FSyncTree calcCompleteEventCacheAtPath:excludeWriteIds:]", + "coverage": 0 + }, + { + "name": "__58-[FSyncTree calcCompleteEventCacheAtPath:excludeWriteIds:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSyncTree collectDistinctViewsForSubTree:]", + "coverage": 1 + }, + { + "name": "__44-[FSyncTree collectDistinctViewsForSubTree:]_block_invoke", + "coverage": 0.875 + }, + { + "name": "__44-[FSyncTree collectDistinctViewsForSubTree:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FSyncTree removeTags:]", + "coverage": 1 + }, + { + "name": "__24-[FSyncTree removeTags:]_block_invoke", + "coverage": 0.375 + }, + { + "name": "-[FSyncTree queryForListening:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree setupListenerOnQuery:view:]", + "coverage": 1 + }, + { + "name": "__39-[FSyncTree setupListenerOnQuery:view:]_block_invoke", + "coverage": 1 + }, + { + "name": "__39-[FSyncTree setupListenerOnQuery:view:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FSyncTree createListenerForView:]", + "coverage": 1 + }, + { + "name": "__35-[FSyncTree createListenerForView:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FSyncTree queryForTag:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree tagForQuery:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree applyOperationToSyncPoints:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree applyOperationHelper:syncPointTree:serverCache:writesCache:]", + "coverage": 1 + }, + { + "name": "-[FSyncTree applyOperationDescendantsHelper:syncPointTree:serverCache:writesCache:]", + "coverage": 1 + }, + { + "name": "__83-[FSyncTree applyOperationDescendantsHelper:syncPointTree:serverCache:writesCache:]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FWriteTree.m", + "coverage": 0.7731958762886598, + "type": "objc", + "functions": [ + { + "name": "-[FWriteTree init]", + "coverage": 1 + }, + { + "name": "-[FWriteTree childWritesForPath:]", + "coverage": 1 + }, + { + "name": "-[FWriteTree addOverwriteAtPath:newData:writeId:isVisible:]", + "coverage": 1 + }, + { + "name": "-[FWriteTree addMergeAtPath:changedChildren:writeId:]", + "coverage": 1 + }, + { + "name": "-[FWriteTree writeForId:]", + "coverage": 1 + }, + { + "name": "__25-[FWriteTree writeForId:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FWriteTree removeWriteId:]", + "coverage": 1 + }, + { + "name": "__28-[FWriteTree removeWriteId:]_block_invoke", + "coverage": 1 + }, + { + "name": "__28-[FWriteTree removeWriteId:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FWriteTree removeAllWrites]", + "coverage": 0 + }, + { + "name": "-[FWriteTree completeWriteDataAtPath:]", + "coverage": 0 + }, + { + "name": "-[FWriteTree calculateCompleteEventCacheAtPath:completeServerCache:excludeWriteIds:includeHiddenWrites:]", + "coverage": 0.46153846153846156 + }, + { + "name": "__104-[FWriteTree calculateCompleteEventCacheAtPath:completeServerCache:excludeWriteIds:includeHiddenWrites:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FWriteTree calculateCompleteEventChildrenAtPath:completeServerChildren:]", + "coverage": 0.7142857142857143 + }, + { + "name": "__74-[FWriteTree calculateCompleteEventChildrenAtPath:completeServerChildren:]_block_invoke", + "coverage": 0 + }, + { + "name": "__74-[FWriteTree calculateCompleteEventChildrenAtPath:completeServerChildren:]_block_invoke.124", + "coverage": 1 + }, + { + "name": "-[FWriteTree calculateEventCacheAfterServerOverwriteAtPath:childPath:existingEventSnap:existingServerSnap:]", + "coverage": 0.88 + }, + { + "name": "-[FWriteTree calculateCompleteChildAtPath:childKey:cache:]", + "coverage": 1 + }, + { + "name": "-[FWriteTree shadowingWriteAtPath:]", + "coverage": 1 + }, + { + "name": "-[FWriteTree calculateNextNodeAfterPost:atPath:completeServerData:reverse:index:]", + "coverage": 0.9285714285714286 + }, + { + "name": "__81-[FWriteTree calculateNextNodeAfterPost:atPath:completeServerData:reverse:index:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FWriteTree record:containsPath:]", + "coverage": 0.4166666666666667 + }, + { + "name": "__34-[FWriteTree record:containsPath:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FWriteTree resetTree]", + "coverage": 0.7777777777777778 + }, + { + "name": "+[FWriteTree defaultFilter]", + "coverage": 1 + }, + { + "name": "__27+[FWriteTree defaultFilter]_block_invoke", + "coverage": 1 + }, + { + "name": "__27+[FWriteTree defaultFilter]_block_invoke_2", + "coverage": 1 + }, + { + "name": "+[FWriteTree layerTreeFromWrites:filter:treeRoot:]", + "coverage": 1 + }, + { + "name": "__50+[FWriteTree layerTreeFromWrites:filter:treeRoot:]_block_invoke", + "coverage": 0.3684210526315789 + } + ] + }, + { + "name": "FIRMutableData.m", + "coverage": 0.7831325301204819, + "type": "objc", + "functions": [ + { + "name": "-[FIRMutableData initWithNode:]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData initWithPrefixPath:andSnapshotHolder:]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData childDataByAppendingPath:]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData parent]", + "coverage": 0 + }, + { + "name": "-[FIRMutableData setValue:]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData setPriority:]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData value]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData priority]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData hasChildren]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData hasChildAtPath:]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData childrenCount]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData key]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData nodeValue]", + "coverage": 1 + }, + { + "name": "-[FIRMutableData children]", + "coverage": 1 + }, + { + "name": "__26-[FIRMutableData children]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRMutableData isEqualToData:]", + "coverage": 0 + }, + { + "name": "-[FIRMutableData description]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDependency.m", + "coverage": 0.7857142857142857, + "type": "objc", + "functions": [ + { + "name": "+[FIRDependency dependencyWithProtocol:]", + "coverage": 0 + }, + { + "name": "+[FIRDependency dependencyWithProtocol:isRequired:]", + "coverage": 1 + }, + { + "name": "-[FIRDependency initWithProtocol:isRequired:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRBundleUtil.m", + "coverage": 0.7872340425531915, + "type": "objc", + "functions": [ + { + "name": "+[FIRBundleUtil relevantBundles]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil optionsDictionaryPathWithResourceName:andFileType:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil relevantURLSchemes]", + "coverage": 0 + }, + { + "name": "+[FIRBundleUtil hasBundleIdentifierPrefix:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil bundleIdentifierByRemovingLastPartFrom:]", + "coverage": 1 + } + ] + }, + { + "name": "FRepoInfo.m", + "coverage": 0.7931034482758621, + "type": "objc", + "functions": [ + { + "name": "-[FRepoInfo initWithHost:isSecure:withNamespace:]", + "coverage": 0.9473684210526315 + }, + { + "name": "-[FRepoInfo description]", + "coverage": 1 + }, + { + "name": "-[FRepoInfo setInternalHost:]", + "coverage": 0 + }, + { + "name": "-[FRepoInfo clearInternalHostCache]", + "coverage": 1 + }, + { + "name": "-[FRepoInfo isDemoHost]", + "coverage": 0 + }, + { + "name": "-[FRepoInfo isCustomHost]", + "coverage": 0 + }, + { + "name": "-[FRepoInfo connectionURL]", + "coverage": 1 + }, + { + "name": "-[FRepoInfo connectionURLWithLastSessionID:]", + "coverage": 1 + }, + { + "name": "-[FRepoInfo copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FRepoInfo hash]", + "coverage": 1 + }, + { + "name": "-[FRepoInfo isEqual:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAuthInteropFake.m", + "coverage": 0.8, + "type": "objc", + "functions": [ + { + "name": "-[FIRAuthInteropFake initWithToken:userID:error:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthInteropFake getTokenForcingRefresh:withCallback:]", + "coverage": 1 + }, + { + "name": "-[FIRAuthInteropFake getUserID]", + "coverage": 0 + } + ] + }, + { + "name": "FTrackedQuery.m", + "coverage": 0.8070175438596491, + "type": "objc", + "functions": [ + { + "name": "-[FTrackedQuery initWithId:query:lastUse:isActive:isComplete:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQuery initWithId:query:lastUse:isActive:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQuery updateLastUse:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQuery setComplete]", + "coverage": 1 + }, + { + "name": "-[FTrackedQuery setActiveState:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQuery isEqual:]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[FTrackedQuery hash]", + "coverage": 0 + } + ] + }, + { + "name": "FLevelDBStorageEngine.m", + "coverage": 0.8074935400516796, + "type": "objc", + "functions": [ + { + "name": "writeRecordKey", + "coverage": 1 + }, + { + "name": "serverCacheKey", + "coverage": 1 + }, + { + "name": "trackedQueryKey", + "coverage": 1 + }, + { + "name": "trackedQueryKeysKeyPrefix", + "coverage": 1 + }, + { + "name": "trackedQueryKeysKey", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine initWithPath:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine runMigration]", + "coverage": 0.7222222222222222 + }, + { + "name": "-[FLevelDBStorageEngine runLegacyMigration:]", + "coverage": 0 + }, + { + "name": "__44-[FLevelDBStorageEngine runLegacyMigration:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FLevelDBStorageEngine openDatabases]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine purgeDatabase:]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FLevelDBStorageEngine purgeEverything]", + "coverage": 1 + }, + { + "name": "__40-[FLevelDBStorageEngine purgeEverything]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine close]", + "coverage": 1 + }, + { + "name": "+[FLevelDBStorageEngine firebaseDir]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine createDB:]", + "coverage": 0.8636363636363636 + }, + { + "name": "-[FLevelDBStorageEngine saveUserOverwrite:atPath:writeId:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine saveUserMerge:atPath:writeId:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine removeUserWrite:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine removeAllUserWrites]", + "coverage": 0.9333333333333333 + }, + { + "name": "__44-[FLevelDBStorageEngine removeAllUserWrites]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine userWrites]", + "coverage": 1 + }, + { + "name": "__35-[FLevelDBStorageEngine userWrites]_block_invoke", + "coverage": 0.7 + }, + { + "name": "__35-[FLevelDBStorageEngine userWrites]_block_invoke.287", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FLevelDBStorageEngine serverCacheAtPath:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine serverCacheForKeys:atPath:]", + "coverage": 0 + }, + { + "name": "__51-[FLevelDBStorageEngine serverCacheForKeys:atPath:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FLevelDBStorageEngine updateServerCache:atPath:merge:]", + "coverage": 0.72 + }, + { + "name": "__56-[FLevelDBStorageEngine updateServerCache:atPath:merge:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FLevelDBStorageEngine updateServerCacheWithMerge:atPath:]", + "coverage": 0.9444444444444444 + }, + { + "name": "__59-[FLevelDBStorageEngine updateServerCacheWithMerge:atPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine saveNodeInternal:atPath:batch:counter:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine serverCacheEstimatedSizeInBytes]", + "coverage": 0 + }, + { + "name": "-[FLevelDBStorageEngine pruneCache:atPath:]", + "coverage": 0.9629629629629629 + }, + { + "name": "__43-[FLevelDBStorageEngine pruneCache:atPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine loadTrackedQueries]", + "coverage": 1 + }, + { + "name": "__43-[FLevelDBStorageEngine loadTrackedQueries]_block_invoke", + "coverage": 0.7096774193548387 + }, + { + "name": "-[FLevelDBStorageEngine removeTrackedQuery:]", + "coverage": 0.95 + }, + { + "name": "__44-[FLevelDBStorageEngine removeTrackedQuery:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine saveTrackedQuery:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine setTrackedQueryKeys:forQueryId:]", + "coverage": 0.972972972972973 + }, + { + "name": "__56-[FLevelDBStorageEngine setTrackedQueryKeys:forQueryId:]_block_invoke", + "coverage": 1 + }, + { + "name": "__56-[FLevelDBStorageEngine setTrackedQueryKeys:forQueryId:]_block_invoke.460", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine updateTrackedQueryKeysWithAddedKeys:removedKeys:forQueryId:]", + "coverage": 0.9375 + }, + { + "name": "__84-[FLevelDBStorageEngine updateTrackedQueryKeysWithAddedKeys:removedKeys:forQueryId:]_block_invoke", + "coverage": 1 + }, + { + "name": "__84-[FLevelDBStorageEngine updateTrackedQueryKeysWithAddedKeys:removedKeys:forQueryId:]_block_invoke.477", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine trackedQueryKeysForQuery:]", + "coverage": 1 + }, + { + "name": "__50-[FLevelDBStorageEngine trackedQueryKeysForQuery:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine removeAllLeafNodesOnPath:batch:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine removeAllWithPrefix:batch:database:]", + "coverage": 1 + }, + { + "name": "__60-[FLevelDBStorageEngine removeAllWithPrefix:batch:database:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine internalSetNestedData:forKey:withBatch:counter:]", + "coverage": 1 + }, + { + "name": "__72-[FLevelDBStorageEngine internalSetNestedData:forKey:withBatch:counter:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine internalNestedDataForPath:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine internalNestedDataFromIterator:andKeyPrefix:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine serializePrimitive:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine fixDoubleParsing:]", + "coverage": 1 + }, + { + "name": "-[FLevelDBStorageEngine deserializePrimitive:]", + "coverage": 0.8235294117647058 + }, + { + "name": "+[FLevelDBStorageEngine ensureDir:markAsDoNotBackup:]", + "coverage": 0.7619047619047619 + } + ] + }, + { + "name": "FIRConfiguration.m", + "coverage": 0.8076923076923077, + "type": "objc", + "functions": [ + { + "name": "+[FIRConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__34+[FIRConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration init]", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration setLoggerLevel:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRLogger.m", + "coverage": 0.8118811881188119, + "type": "objc", + "functions": [ + { + "name": "FIRLoggerInitializeASL", + "coverage": 1 + }, + { + "name": "__FIRLoggerInitializeASL_block_invoke", + "coverage": 0.9333333333333333 + }, + { + "name": "FIRSetAnalyticsDebugMode", + "coverage": 0 + }, + { + "name": "FIRSetLoggerLevel", + "coverage": 0 + }, + { + "name": "FIRResetLogger", + "coverage": 1 + }, + { + "name": "FIRSetLoggerUserDefaults", + "coverage": 1 + }, + { + "name": "FIRIsLoggableLevel", + "coverage": 0 + }, + { + "name": "FIRLogBasic", + "coverage": 1 + }, + { + "name": "Definition at 158:95", + "coverage": 1 + }, + { + "name": "+[FIRLoggerWrapper logWithLevel:withService:withCode:withMessage:withArgs:]", + "coverage": 0 + } + ] + }, + { + "name": "FLeafNode.m", + "coverage": 0.8435754189944135, + "type": "objc", + "functions": [ + { + "name": "-[FLeafNode initWithValue:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode initWithValue:withPriority:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode isLeafNode]", + "coverage": 1 + }, + { + "name": "-[FLeafNode getPriority]", + "coverage": 1 + }, + { + "name": "-[FLeafNode updatePriority:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode getImmediateChild:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode getChild:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode hasChild:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode predecessorChildKey:]", + "coverage": 0 + }, + { + "name": "-[FLeafNode updateImmediateChild:withNewChild:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode updateChild:withNewChild:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode val]", + "coverage": 1 + }, + { + "name": "-[FLeafNode valForExport:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FLeafNode isEqual:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode hash]", + "coverage": 1 + }, + { + "name": "-[FLeafNode withIndex:]", + "coverage": 0 + }, + { + "name": "-[FLeafNode isIndexed:]", + "coverage": 0 + }, + { + "name": "-[FLeafNode isEmpty]", + "coverage": 1 + }, + { + "name": "-[FLeafNode numChildren]", + "coverage": 1 + }, + { + "name": "-[FLeafNode enumerateChildrenUsingBlock:]", + "coverage": 1 + }, + { + "name": "-[FLeafNode enumerateChildrenReverse:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[FLeafNode childEnumerator]", + "coverage": 0 + }, + { + "name": "-[FLeafNode dataHash]", + "coverage": 1 + }, + { + "name": "-[FLeafNode compare:]", + "coverage": 1 + }, + { + "name": "+[FLeafNode valueTypeOrder]", + "coverage": 1 + }, + { + "name": "__27+[FLeafNode valueTypeOrder]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLeafNode compareToLeafNode:]", + "coverage": 0.9 + }, + { + "name": "-[FLeafNode description]", + "coverage": 0 + }, + { + "name": "-[FLeafNode forEachChildDo:]", + "coverage": 0 + } + ] + }, + { + "name": "FImmutableTree.m", + "coverage": 0.8474114441416893, + "type": "objc", + "functions": [ + { + "name": "-[FImmutableTree initWithValue:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree initWithValue:children:]", + "coverage": 1 + }, + { + "name": "+[FImmutableTree emptyChildren]", + "coverage": 1 + }, + { + "name": "__31+[FImmutableTree emptyChildren]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FImmutableTree empty]", + "coverage": 1 + }, + { + "name": "__23+[FImmutableTree empty]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableTree isEmpty]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree findRootMostMatchingPath:predicate:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree findRootMostValueAndPath:]", + "coverage": 1 + }, + { + "name": "__43-[FImmutableTree findRootMostValueAndPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableTree rootMostValueOnPath:]", + "coverage": 1 + }, + { + "name": "__38-[FImmutableTree rootMostValueOnPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableTree rootMostValueOnPath:matching:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree leafMostValueOnPath:]", + "coverage": 1 + }, + { + "name": "__38-[FImmutableTree leafMostValueOnPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableTree leafMostValueOnPath:matching:]", + "coverage": 1 + }, + { + "name": "__47-[FImmutableTree leafMostValueOnPath:matching:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableTree containsValueMatching:]", + "coverage": 1 + }, + { + "name": "__40-[FImmutableTree containsValueMatching:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableTree subtreeAtPath:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree setValue:atPath:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree removeValueAtPath:]", + "coverage": 0.7857142857142857 + }, + { + "name": "-[FImmutableTree valueAtPath:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree setTree:atPath:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree foldWithBlock:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree foldWithPathSoFar:withBlock:]", + "coverage": 1 + }, + { + "name": "__46-[FImmutableTree foldWithPathSoFar:withBlock:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableTree findOnPath:andApplyBlock:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree findOnPath:pathSoFar:andApplyBlock:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FImmutableTree forEachOnPath:whileBlock:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree forEachOnPath:pathSoFar:whileBlock:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree forEachOnPath:performBlock:]", + "coverage": 0 + }, + { + "name": "-[FImmutableTree forEachOnPath:pathSoFar:performBlock:]", + "coverage": 0 + }, + { + "name": "-[FImmutableTree forEach:]", + "coverage": 1 + }, + { + "name": "-[FImmutableTree forEachPathSoFar:withBlock:]", + "coverage": 1 + }, + { + "name": "__45-[FImmutableTree forEachPathSoFar:withBlock:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableTree forEachChild:]", + "coverage": 1 + }, + { + "name": "__31-[FImmutableTree forEachChild:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FImmutableTree isEqual:]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FImmutableTree hash]", + "coverage": 0 + }, + { + "name": "-[FImmutableTree description]", + "coverage": 0 + }, + { + "name": "__29-[FImmutableTree description]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FImmutableTree debugDescription]", + "coverage": 0 + } + ] + }, + { + "name": "FPriorityIndex.m", + "coverage": 0.8507462686567164, + "type": "objc", + "functions": [ + { + "name": "-[FPriorityIndex compareKey:andNode:toOtherKey:andNode:]", + "coverage": 1 + }, + { + "name": "-[FPriorityIndex compareKey:andNode:toOtherKey:andNode:reverse:]", + "coverage": 1 + }, + { + "name": "-[FPriorityIndex compareNamedNode:toNamedNode:]", + "coverage": 1 + }, + { + "name": "-[FPriorityIndex isDefinedOn:]", + "coverage": 1 + }, + { + "name": "-[FPriorityIndex indexedValueChangedBetween:and:]", + "coverage": 1 + }, + { + "name": "-[FPriorityIndex minPost]", + "coverage": 1 + }, + { + "name": "-[FPriorityIndex maxPost]", + "coverage": 1 + }, + { + "name": "-[FPriorityIndex makePost:name:]", + "coverage": 1 + }, + { + "name": "-[FPriorityIndex queryDefinition]", + "coverage": 0 + }, + { + "name": "-[FPriorityIndex description]", + "coverage": 0 + }, + { + "name": "-[FPriorityIndex copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[FPriorityIndex isEqual:]", + "coverage": 1 + }, + { + "name": "-[FPriorityIndex hash]", + "coverage": 1 + }, + { + "name": "+[FPriorityIndex priorityIndex]", + "coverage": 1 + }, + { + "name": "__31+[FPriorityIndex priorityIndex]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "NSData+SRB64Additions.m", + "coverage": 0.8571428571428571, + "type": "objc", + "functions": [ + { + "name": "+[FSRUtilities base64EncodedStringFromData:]", + "coverage": 0.8571428571428571 + } + ] + }, + { + "name": "FLLRBValueNode.m", + "coverage": 0.8601036269430051, + "type": "objc", + "functions": [ + { + "name": "-[FLLRBValueNode description]", + "coverage": 0 + }, + { + "name": "-[FLLRBValueNode initWithKey:withValue:withColor:withLeft:withRight:]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode copyWith:withValue:withColor:withLeft:withRight:]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode count]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode isEmpty]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode inorderTraversal:]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode reverseTraversal:]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode min]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode minKey]", + "coverage": 0 + }, + { + "name": "-[FLLRBValueNode maxKey]", + "coverage": 0 + }, + { + "name": "-[FLLRBValueNode insertKey:forValue:withComparator:]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode removeMin]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode fixUp]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode moveRedLeft]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode moveRedRight]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode rotateLeft]", + "coverage": 0.75 + }, + { + "name": "-[FLLRBValueNode rotateRight]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode colorFlip]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode remove:withComparator:]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode isRed]", + "coverage": 1 + }, + { + "name": "-[FLLRBValueNode checkMaxDepth]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FLLRBValueNode check]", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "FView.m", + "coverage": 0.8642857142857143, + "type": "objc", + "functions": [ + { + "name": "-[FViewOperationResult initWithChanges:events:]", + "coverage": 1 + }, + { + "name": "-[FView initWithQuery:initialViewCache:]", + "coverage": 1 + }, + { + "name": "-[FView serverCache]", + "coverage": 0 + }, + { + "name": "-[FView eventCache]", + "coverage": 1 + }, + { + "name": "-[FView completeServerCacheFor:]", + "coverage": 1 + }, + { + "name": "-[FView isEmpty]", + "coverage": 1 + }, + { + "name": "-[FView addEventRegistration:]", + "coverage": 1 + }, + { + "name": "-[FView removeEventRegistration:cancelError:]", + "coverage": 0.5357142857142857 + }, + { + "name": "-[FView applyOperation:writesCache:serverCache:]", + "coverage": 1 + }, + { + "name": "-[FView initialEvents:]", + "coverage": 1 + }, + { + "name": "__23-[FView initialEvents:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FView generateEventsForChanges:eventCache:registration:]", + "coverage": 1 + }, + { + "name": "-[FView description]", + "coverage": 0 + } + ] + }, + { + "name": "FPathIndex.m", + "coverage": 0.8714285714285714, + "type": "objc", + "functions": [ + { + "name": "-[FPathIndex initWithPath:]", + "coverage": 0.8 + }, + { + "name": "-[FPathIndex compareKey:andNode:toOtherKey:andNode:]", + "coverage": 1 + }, + { + "name": "-[FPathIndex compareKey:andNode:toOtherKey:andNode:reverse:]", + "coverage": 1 + }, + { + "name": "-[FPathIndex compareNamedNode:toNamedNode:]", + "coverage": 1 + }, + { + "name": "-[FPathIndex isDefinedOn:]", + "coverage": 1 + }, + { + "name": "-[FPathIndex indexedValueChangedBetween:and:]", + "coverage": 1 + }, + { + "name": "-[FPathIndex minPost]", + "coverage": 1 + }, + { + "name": "-[FPathIndex maxPost]", + "coverage": 1 + }, + { + "name": "-[FPathIndex makePost:name:]", + "coverage": 1 + }, + { + "name": "-[FPathIndex queryDefinition]", + "coverage": 1 + }, + { + "name": "-[FPathIndex description]", + "coverage": 0 + }, + { + "name": "-[FPathIndex copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[FPathIndex isEqual:]", + "coverage": 1 + }, + { + "name": "-[FPathIndex hash]", + "coverage": 1 + } + ] + }, + { + "name": "FQuerySpec.m", + "coverage": 0.8723404255319149, + "type": "objc", + "functions": [ + { + "name": "-[FQuerySpec initWithPath:params:]", + "coverage": 1 + }, + { + "name": "+[FQuerySpec defaultQueryAtPath:]", + "coverage": 1 + }, + { + "name": "-[FQuerySpec copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FQuerySpec index]", + "coverage": 1 + }, + { + "name": "-[FQuerySpec isDefault]", + "coverage": 1 + }, + { + "name": "-[FQuerySpec loadsAllData]", + "coverage": 1 + }, + { + "name": "-[FQuerySpec isEqual:]", + "coverage": 0.6470588235294118 + }, + { + "name": "-[FQuerySpec hash]", + "coverage": 1 + }, + { + "name": "-[FQuerySpec description]", + "coverage": 1 + } + ] + }, + { + "name": "FOverwrite.m", + "coverage": 0.875, + "type": "objc", + "functions": [ + { + "name": "-[FOverwrite initWithSource:path:snap:]", + "coverage": 1 + }, + { + "name": "-[FOverwrite operationForChild:]", + "coverage": 1 + }, + { + "name": "-[FOverwrite description]", + "coverage": 0 + } + ] + }, + { + "name": "FIRRetryHelper.m", + "coverage": 0.8809523809523809, + "type": "objc", + "functions": [ + { + "name": "-[FIRRetryHelperTask initWithBlock:]", + "coverage": 1 + }, + { + "name": "-[FIRRetryHelperTask isCanceled]", + "coverage": 1 + }, + { + "name": "-[FIRRetryHelperTask cancel]", + "coverage": 0 + }, + { + "name": "-[FIRRetryHelperTask execute]", + "coverage": 1 + }, + { + "name": "-[FIRRetryHelper initWithDispatchQueue:minRetryDelayAfterFailure:maxRetryDelay:retryExponent:jitterFactor:]", + "coverage": 1 + }, + { + "name": "-[FIRRetryHelper retry:]", + "coverage": 0.8823529411764706 + }, + { + "name": "__24-[FIRRetryHelper retry:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRRetryHelper signalSuccess]", + "coverage": 1 + }, + { + "name": "-[FIRRetryHelper cancel]", + "coverage": 0.7 + } + ] + }, + { + "name": "FIRApp.m", + "coverage": 0.8848, + "type": "objc", + "functions": [ + { + "name": "+[FIRApp configure]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithOptions:]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithName:options:]", + "coverage": 0.9333333333333333 + }, + { + "name": "+[FIRApp defaultApp]", + "coverage": 1 + }, + { + "name": "+[FIRApp appNamed:]", + "coverage": 1 + }, + { + "name": "+[FIRApp allApps]", + "coverage": 1 + }, + { + "name": "+[FIRApp resetApps]", + "coverage": 1 + }, + { + "name": "-[FIRApp deleteApp:]", + "coverage": 0.7916666666666666 + }, + { + "name": "+[FIRApp addAppToAppDictionary:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRApp initInstanceWithName:options:]", + "coverage": 1 + }, + { + "name": "-[FIRApp configureCore]", + "coverage": 0.7962962962962963 + }, + { + "name": "-[FIRApp options]", + "coverage": 1 + }, + { + "name": "-[FIRApp setDataCollectionDefaultEnabled:]", + "coverage": 0.9354838709677419 + }, + { + "name": "-[FIRApp isDataCollectionDefaultEnabled]", + "coverage": 1 + }, + { + "name": "+[FIRApp sendNotificationsToSDKs:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForMissingOptions]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForSubspecConfigurationFailureWithDomain:errorCode:service:reason:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForInvalidAppID]", + "coverage": 0 + }, + { + "name": "+[FIRApp isDefaultAppConfigured]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerLibrary:withVersion:]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerInternalLibrary:withName:withVersion:]", + "coverage": 1 + }, + { + "name": "__55+[FIRApp registerInternalLibrary:withName:withVersion:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRApp firebaseUserAgent]", + "coverage": 1 + }, + { + "name": "-[FIRApp checkExpectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp isAppIDValid]", + "coverage": 1 + }, + { + "name": "+[FIRApp validateAppID:]", + "coverage": 0.9473684210526315 + }, + { + "name": "+[FIRApp actualBundleID]", + "coverage": 0 + }, + { + "name": "+[FIRApp validateAppIDFormat:withVersion:]", + "coverage": 0.8450704225352113 + }, + { + "name": "+[FIRApp validateAppIDFingerprint:withVersion:]", + "coverage": 0.8620689655172413 + }, + { + "name": "-[FIRApp expectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp clearDataCollectionSwitchFromUserDefaults]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromUserDefaultsForApp:]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromPlist]", + "coverage": 1 + }, + { + "name": "__43+[FIRApp readDataCollectionSwitchFromPlist]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRApp sendLogsWithServiceName:version:error:]", + "coverage": 0.2631578947368421 + } + ] + }, + { + "name": "FChange.m", + "coverage": 0.8888888888888888, + "type": "objc", + "functions": [ + { + "name": "-[FChange initWithType:indexedNode:]", + "coverage": 1 + }, + { + "name": "-[FChange initWithType:indexedNode:childKey:]", + "coverage": 1 + }, + { + "name": "-[FChange initWithType:indexedNode:childKey:oldIndexedNode:]", + "coverage": 1 + }, + { + "name": "-[FChange changeWithPrevKey:]", + "coverage": 1 + }, + { + "name": "-[FChange description]", + "coverage": 0 + } + ] + }, + { + "name": "FAckUserWrite.m", + "coverage": 0.8888888888888888, + "type": "objc", + "functions": [ + { + "name": "-[FAckUserWrite initWithPath:affectedTree:revert:]", + "coverage": 1 + }, + { + "name": "-[FAckUserWrite operationForChild:]", + "coverage": 1 + }, + { + "name": "-[FAckUserWrite description]", + "coverage": 0 + } + ] + }, + { + "name": "FOperationSource.m", + "coverage": 0.8974358974358975, + "type": "objc", + "functions": [ + { + "name": "-[FOperationSource initWithFromUser:fromServer:queryParams:tagged:]", + "coverage": 1 + }, + { + "name": "+[FOperationSource userInstance]", + "coverage": 1 + }, + { + "name": "__32+[FOperationSource userInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FOperationSource serverInstance]", + "coverage": 1 + }, + { + "name": "__34+[FOperationSource serverInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FOperationSource forServerTaggedQuery:]", + "coverage": 1 + }, + { + "name": "-[FOperationSource description]", + "coverage": 0 + } + ] + }, + { + "name": "FMerge.m", + "coverage": 0.9, + "type": "objc", + "functions": [ + { + "name": "-[FMerge initWithSource:path:children:]", + "coverage": 1 + }, + { + "name": "-[FMerge operationForChild:]", + "coverage": 1 + }, + { + "name": "-[FMerge description]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDataSnapshot.m", + "coverage": 0.9016393442622951, + "type": "objc", + "functions": [ + { + "name": "-[FIRDataSnapshot initWithRef:indexedNode:]", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot value]", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot valueInExportFormat]", + "coverage": 0 + }, + { + "name": "-[FIRDataSnapshot childSnapshotForPath:]", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot hasChild:]", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot priority]", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot hasChildren]", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot exists]", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot key]", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot childrenCount]", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot children]", + "coverage": 1 + }, + { + "name": "__27-[FIRDataSnapshot children]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRDataSnapshot description]", + "coverage": 0 + } + ] + }, + { + "name": "FPruneForest.m", + "coverage": 0.9041095890410958, + "type": "objc", + "functions": [ + { + "name": "kFPrunePredicate_block_invoke", + "coverage": 1 + }, + { + "name": "kFKeepPredicate_block_invoke_2", + "coverage": 1 + }, + { + "name": "+[FPruneForest pruneTree]", + "coverage": 1 + }, + { + "name": "__25+[FPruneForest pruneTree]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FPruneForest keepTree]", + "coverage": 1 + }, + { + "name": "__24+[FPruneForest keepTree]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FPruneForest initWithForest:]", + "coverage": 1 + }, + { + "name": "+[FPruneForest empty]", + "coverage": 1 + }, + { + "name": "__21+[FPruneForest empty]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FPruneForest prunesAnything]", + "coverage": 1 + }, + { + "name": "-[FPruneForest shouldPruneUnkeptDescendantsAtPath:]", + "coverage": 1 + }, + { + "name": "-[FPruneForest shouldKeepPath:]", + "coverage": 0 + }, + { + "name": "-[FPruneForest affectsPath:]", + "coverage": 1 + }, + { + "name": "-[FPruneForest child:]", + "coverage": 0.8666666666666667 + }, + { + "name": "-[FPruneForest childAtPath:]", + "coverage": 1 + }, + { + "name": "-[FPruneForest prunePath:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FPruneForest keepPath:]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FPruneForest keepAll:atPath:]", + "coverage": 0.75 + }, + { + "name": "-[FPruneForest pruneAll:atPath:]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FPruneForest setPruneValue:forAll:atPath:]", + "coverage": 1 + }, + { + "name": "__44-[FPruneForest setPruneValue:forAll:atPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FPruneForest enumarateKeptNodesUsingBlock:]", + "coverage": 1 + }, + { + "name": "__45-[FPruneForest enumarateKeptNodesUsingBlock:]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FChildrenNode.m", + "coverage": 0.9064171122994652, + "type": "objc", + "functions": [ + { + "name": "-[FChildrenNode init]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode initWithChildren:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode initWithPriority:children:]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FChildrenNode description]", + "coverage": 0 + }, + { + "name": "-[FChildrenNode isLeafNode]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode getPriority]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode updatePriority:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode getImmediateChild:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode getChild:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode hasChild:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode updateImmediateChild:withNewChild:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode updateChild:withNewChild:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode isEmpty]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode numChildren]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode val]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode valForExport:]", + "coverage": 0.7627118644067796 + }, + { + "name": "__30-[FChildrenNode valForExport:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FChildrenNode dataHash]", + "coverage": 1 + }, + { + "name": "__25-[FChildrenNode dataHash]_block_invoke", + "coverage": 1 + }, + { + "name": "__25-[FChildrenNode dataHash]_block_invoke.123", + "coverage": 1 + }, + { + "name": "__25-[FChildrenNode dataHash]_block_invoke.130", + "coverage": 1 + }, + { + "name": "__25-[FChildrenNode dataHash]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__25-[FChildrenNode dataHash]_block_invoke.158", + "coverage": 1 + }, + { + "name": "-[FChildrenNode compare:]", + "coverage": 0.5294117647058824 + }, + { + "name": "-[FChildrenNode isEqual:]", + "coverage": 0.9655172413793104 + }, + { + "name": "__25-[FChildrenNode isEqual:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FChildrenNode hash]", + "coverage": 1 + }, + { + "name": "__21-[FChildrenNode hash]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FChildrenNode enumerateChildrenAndPriorityUsingBlock:]", + "coverage": 1 + }, + { + "name": "__56-[FChildrenNode enumerateChildrenAndPriorityUsingBlock:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FChildrenNode enumerateChildrenUsingBlock:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode enumerateChildrenReverse:usingBlock:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode childEnumerator]", + "coverage": 1 + }, + { + "name": "__32-[FChildrenNode childEnumerator]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FChildrenNode predecessorChildKey:]", + "coverage": 1 + }, + { + "name": "-[FChildrenNode childrenGetter:]", + "coverage": 0 + }, + { + "name": "-[FChildrenNode firstChild]", + "coverage": 0.75 + }, + { + "name": "-[FChildrenNode lastChild]", + "coverage": 0.75 + } + ] + }, + { + "name": "FCompoundWrite.m", + "coverage": 0.908675799086758, + "type": "objc", + "functions": [ + { + "name": "-[FCompoundWrite initWithWriteTree:]", + "coverage": 1 + }, + { + "name": "+[FCompoundWrite compoundWriteWithValueDictionary:]", + "coverage": 1 + }, + { + "name": "__51+[FCompoundWrite compoundWriteWithValueDictionary:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FCompoundWrite compoundWriteWithNodeDictionary:]", + "coverage": 0 + }, + { + "name": "__50+[FCompoundWrite compoundWriteWithNodeDictionary:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FCompoundWrite emptyWrite]", + "coverage": 1 + }, + { + "name": "__28+[FCompoundWrite emptyWrite]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite addWrite:atPath:]", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite addWrite:atKey:]", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite addCompoundWrite:atPath:]", + "coverage": 1 + }, + { + "name": "__42-[FCompoundWrite addCompoundWrite:atPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite removeWriteAtPath:]", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite hasCompleteWriteAtPath:]", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite completeNodeAtPath:]", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite completeChildren]", + "coverage": 1 + }, + { + "name": "__34-[FCompoundWrite completeChildren]_block_invoke", + "coverage": 1 + }, + { + "name": "__34-[FCompoundWrite completeChildren]_block_invoke.73", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite childCompoundWrites]", + "coverage": 1 + }, + { + "name": "__37-[FCompoundWrite childCompoundWrites]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite childCompoundWriteAtPath:]", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite applySubtreeWrite:atPath:toNode:]", + "coverage": 1 + }, + { + "name": "__50-[FCompoundWrite applySubtreeWrite:atPath:toNode:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite enumerateWrites:]", + "coverage": 1 + }, + { + "name": "__34-[FCompoundWrite enumerateWrites:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite applyToNode:]", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite isEmpty]", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite rootWrite]", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite isEqual:]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FCompoundWrite hash]", + "coverage": 0 + }, + { + "name": "-[FCompoundWrite valForExport:]", + "coverage": 1 + }, + { + "name": "__31-[FCompoundWrite valForExport:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FCompoundWrite description]", + "coverage": 0 + } + ] + }, + { + "name": "FIndex.m", + "coverage": 0.9090909090909091, + "type": "objc", + "functions": [ + { + "name": "+[FIndex indexFromQueryDefinition:]", + "coverage": 0.9090909090909091 + } + ] + }, + { + "name": "FTrackedQueryManager.m", + "coverage": 0.9108635097493036, + "type": "objc", + "functions": [ + { + "name": "-[FTrackedQueryManager initWithStorageEngine:clock:]", + "coverage": 1 + }, + { + "name": "__52-[FTrackedQueryManager initWithStorageEngine:clock:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FTrackedQueryManager assertValidTrackedQuery:]", + "coverage": 1 + }, + { + "name": "+[FTrackedQueryManager normalizeQuery:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager findTrackedQuery:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager removeTrackedQuery:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager setQueryActive:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager setQueryInactive:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager setQueryActive:forQuery:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager setQueryComplete:]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FTrackedQueryManager setQueriesCompleteAtPath:]", + "coverage": 1 + }, + { + "name": "__49-[FTrackedQueryManager setQueriesCompleteAtPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "__49-[FTrackedQueryManager setQueriesCompleteAtPath:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager isQueryComplete:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager hasActiveDefaultQueryAtPath:]", + "coverage": 1 + }, + { + "name": "__52-[FTrackedQueryManager hasActiveDefaultQueryAtPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager ensureCompleteTrackedQueryAtPath:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager isIncludedInDefaultCompleteQuery:]", + "coverage": 1 + }, + { + "name": "__57-[FTrackedQueryManager isIncludedInDefaultCompleteQuery:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager cacheTrackedQuery:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager numberOfQueriesToPrune:prunableCount:]", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager pruneOldQueries:]", + "coverage": 1 + }, + { + "name": "__40-[FTrackedQueryManager pruneOldQueries:]_block_invoke", + "coverage": 1 + }, + { + "name": "__40-[FTrackedQueryManager pruneOldQueries:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__40-[FTrackedQueryManager pruneOldQueries:]_block_invoke.149", + "coverage": 0.8888888888888888 + }, + { + "name": "__40-[FTrackedQueryManager pruneOldQueries:]_block_invoke.168", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager numberOfPrunableQueries]", + "coverage": 0 + }, + { + "name": "__47-[FTrackedQueryManager numberOfPrunableQueries]_block_invoke", + "coverage": 0 + }, + { + "name": "__47-[FTrackedQueryManager numberOfPrunableQueries]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FTrackedQueryManager filteredQueryIdsAtPath:]", + "coverage": 1 + }, + { + "name": "__47-[FTrackedQueryManager filteredQueryIdsAtPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager knownCompleteChildrenAtPath:]", + "coverage": 1 + }, + { + "name": "__52-[FTrackedQueryManager knownCompleteChildrenAtPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "__52-[FTrackedQueryManager knownCompleteChildrenAtPath:]_block_invoke.206", + "coverage": 1 + }, + { + "name": "-[FTrackedQueryManager verifyCache]", + "coverage": 0.9130434782608695 + }, + { + "name": "__35-[FTrackedQueryManager verifyCache]_block_invoke", + "coverage": 1 + }, + { + "name": "__35-[FTrackedQueryManager verifyCache]_block_invoke.220", + "coverage": 0.7777777777777778 + } + ] + }, + { + "name": "FRangedFilter.m", + "coverage": 0.9130434782608695, + "type": "objc", + "functions": [ + { + "name": "-[FRangedFilter initWithQueryParams:]", + "coverage": 1 + }, + { + "name": "+[FRangedFilter startPostFromQueryParams:]", + "coverage": 1 + }, + { + "name": "+[FRangedFilter endPostFromQueryParams:]", + "coverage": 1 + }, + { + "name": "-[FRangedFilter matchesKey:andNode:]", + "coverage": 1 + }, + { + "name": "-[FRangedFilter updateChildIn:forChildKey:newChild:affectedPath:fromSource:accumulator:]", + "coverage": 1 + }, + { + "name": "-[FRangedFilter updateFullNode:withNewNode:accumulator:]", + "coverage": 0.875 + }, + { + "name": "__56-[FRangedFilter updateFullNode:withNewNode:accumulator:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FRangedFilter updatePriority:forNode:]", + "coverage": 0 + }, + { + "name": "-[FRangedFilter filtersNodes]", + "coverage": 1 + } + ] + }, + { + "name": "FSyncPoint.m", + "coverage": 0.915, + "type": "objc", + "functions": [ + { + "name": "-[FSyncPoint initWithPersistenceManager:]", + "coverage": 1 + }, + { + "name": "-[FSyncPoint isEmpty]", + "coverage": 1 + }, + { + "name": "-[FSyncPoint applyOperation:toView:writesCache:serverCache:]", + "coverage": 1 + }, + { + "name": "__60-[FSyncPoint applyOperation:toView:writesCache:serverCache:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSyncPoint applyOperation:writesCache:serverCache:]", + "coverage": 1 + }, + { + "name": "__53-[FSyncPoint applyOperation:writesCache:serverCache:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSyncPoint addEventRegistration:forNonExistingViewForQuery:writesCache:serverCache:]", + "coverage": 1 + }, + { + "name": "__86-[FSyncPoint addEventRegistration:forNonExistingViewForQuery:writesCache:serverCache:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSyncPoint addEventRegistration:forExistingViewForQuery:]", + "coverage": 1 + }, + { + "name": "-[FSyncPoint removeEventRegistration:forQuery:cancelError:]", + "coverage": 0.6341463414634146 + }, + { + "name": "__59-[FSyncPoint removeEventRegistration:forQuery:cancelError:]_block_invoke", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FSyncPoint queryViews]", + "coverage": 1 + }, + { + "name": "__24-[FSyncPoint queryViews]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSyncPoint completeServerCacheAtPath:]", + "coverage": 1 + }, + { + "name": "__40-[FSyncPoint completeServerCacheAtPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSyncPoint viewForQuery:]", + "coverage": 1 + }, + { + "name": "-[FSyncPoint viewExistsForQuery:]", + "coverage": 1 + }, + { + "name": "-[FSyncPoint hasCompleteView]", + "coverage": 1 + }, + { + "name": "-[FSyncPoint completeView]", + "coverage": 1 + }, + { + "name": "__26-[FSyncPoint completeView]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FViewProcessor.m", + "coverage": 0.923728813559322, + "type": "objc", + "functions": [ + { + "name": "+[FNoCompleteChildSource instance]", + "coverage": 1 + }, + { + "name": "__34+[FNoCompleteChildSource instance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FNoCompleteChildSource completeChild:]", + "coverage": 0 + }, + { + "name": "-[FNoCompleteChildSource childByIndex:afterChild:isReverse:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeCompleteChildSource initWithWrites:viewCache:serverCache:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeCompleteChildSource completeChild:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeCompleteChildSource childByIndex:afterChild:isReverse:]", + "coverage": 1 + }, + { + "name": "-[FViewProcessor initWithFilter:]", + "coverage": 1 + }, + { + "name": "-[FViewProcessor applyOperationOn:operation:writesCache:completeCache:]", + "coverage": 0.875 + }, + { + "name": "-[FViewProcessor maybeAddValueFromOldViewCache:newViewCache:changes:]", + "coverage": 1 + }, + { + "name": "-[FViewProcessor generateEventCacheAfterServerEvent:path:writesCache:source:accumulator:]", + "coverage": 0.918918918918919 + }, + { + "name": "-[FViewProcessor applyServerOverwriteTo:changePath:snap:writesCache:completeCache:filterServerNode:accumulator:]", + "coverage": 0.8367346938775511 + }, + { + "name": "-[FViewProcessor applyUserOverwriteTo:changePath:changedSnap:writesCache:completeCache:accumulator:]", + "coverage": 0.9464285714285714 + }, + { + "name": "+[FViewProcessor cache:hasChild:]", + "coverage": 1 + }, + { + "name": "-[FViewProcessor applyUserMergeTo:path:changedChildren:writesCache:completeCache:accumulator:]", + "coverage": 1 + }, + { + "name": "__94-[FViewProcessor applyUserMergeTo:path:changedChildren:writesCache:completeCache:accumulator:]_block_invoke", + "coverage": 1 + }, + { + "name": "__94-[FViewProcessor applyUserMergeTo:path:changedChildren:writesCache:completeCache:accumulator:]_block_invoke.268", + "coverage": 1 + }, + { + "name": "-[FViewProcessor applyServerMergeTo:path:changedChildren:writesCache:completeCache:filterServerNode:accumulator:]", + "coverage": 1 + }, + { + "name": "__113-[FViewProcessor applyServerMergeTo:path:changedChildren:writesCache:completeCache:filterServerNode:accumulator:]_block_invoke", + "coverage": 1 + }, + { + "name": "__113-[FViewProcessor applyServerMergeTo:path:changedChildren:writesCache:completeCache:filterServerNode:accumulator:]_block_invoke.292", + "coverage": 1 + }, + { + "name": "-[FViewProcessor ackUserWriteOn:ackPath:affectedTree:writesCache:completeCache:accumulator:]", + "coverage": 0.9111111111111111 + }, + { + "name": "__92-[FViewProcessor ackUserWriteOn:ackPath:affectedTree:writesCache:completeCache:accumulator:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FViewProcessor revertUserWriteOn:path:writesCache:completeCache:accumulator:]", + "coverage": 0.9298245614035088 + }, + { + "name": "-[FViewProcessor listenCompleteOldCache:path:writesCache:serverCache:accumulator:]", + "coverage": 0 + } + ] + }, + { + "name": "FIROptions.m", + "coverage": 0.9489795918367347, + "type": "objc", + "functions": [ + { + "name": "+[FIROptions defaultOptions]", + "coverage": 0.8461538461538461 + }, + { + "name": "+[FIROptions initialize]", + "coverage": 1 + }, + { + "name": "+[FIROptions defaultOptionsDictionary]", + "coverage": 1 + }, + { + "name": "+[FIROptions plistFilePathWithName:]", + "coverage": 1 + }, + { + "name": "+[FIROptions resetDefaultOptions]", + "coverage": 1 + }, + { + "name": "-[FIROptions initInternalWithOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithContentsOfFile:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithGoogleAppID:GCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions APIKey]", + "coverage": 1 + }, + { + "name": "-[FIROptions checkEditingLocked]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAPIKey:]", + "coverage": 1 + }, + { + "name": "-[FIROptions clientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions trackingID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setTrackingID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions GCMSenderID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions projectID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setProjectID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions androidClientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAndroidClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions googleAppID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGoogleAppID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions libraryVersionID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setLibraryVersionID:]", + "coverage": 0 + }, + { + "name": "-[FIROptions databaseURL]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDatabaseURL:]", + "coverage": 1 + }, + { + "name": "-[FIROptions storageBucket]", + "coverage": 1 + }, + { + "name": "-[FIROptions setStorageBucket:]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDeepLinkURLScheme:]", + "coverage": 1 + }, + { + "name": "-[FIROptions bundleID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionaryWithInfoDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIROptions isMeasurementEnabled]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIROptions isAnalyticsCollectionExpicitlySet]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionEnabled]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionDeactivated]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsEnabled]", + "coverage": 0 + }, + { + "name": "-[FIROptions isSignInEnabled]", + "coverage": 0 + } + ] + }, + { + "name": "FLimitedFilter.m", + "coverage": 0.9504950495049505, + "type": "objc", + "functions": [ + { + "name": "-[FLimitedFilter initWithQueryParams:]", + "coverage": 1 + }, + { + "name": "-[FLimitedFilter updateChildIn:forChildKey:newChild:affectedPath:fromSource:accumulator:]", + "coverage": 1 + }, + { + "name": "-[FLimitedFilter fullLimitUpdateNode:forChildKey:newChild:fromSource:accumulator:]", + "coverage": 0.9368421052631579 + }, + { + "name": "-[FLimitedFilter updateFullNode:withNewNode:accumulator:]", + "coverage": 1 + }, + { + "name": "__57-[FLimitedFilter updateFullNode:withNewNode:accumulator:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FLimitedFilter updatePriority:forNode:]", + "coverage": 0 + }, + { + "name": "-[FLimitedFilter filtersNodes]", + "coverage": 1 + }, + { + "name": "-[FLimitedFilter indexedFilter]", + "coverage": 1 + } + ] + }, + { + "name": "FEventGenerator.m", + "coverage": 0.9534883720930233, + "type": "objc", + "functions": [ + { + "name": "-[FEventGenerator initWithQuery:]", + "coverage": 1 + }, + { + "name": "-[FEventGenerator generateEventsForChanges:eventCache:eventRegistrations:]", + "coverage": 1 + }, + { + "name": "-[FEventGenerator generateEvents:forType:changes:eventCache:eventRegistrations:]", + "coverage": 1 + }, + { + "name": "__80-[FEventGenerator generateEvents:forType:changes:eventCache:eventRegistrations:]_block_invoke", + "coverage": 0.6363636363636364 + }, + { + "name": "-[FEventGenerator generateEventForChange:registration:eventCache:]", + "coverage": 1 + } + ] + }, + { + "name": "FRangeMerge.m", + "coverage": 0.9545454545454546, + "type": "objc", + "functions": [ + { + "name": "-[FRangeMerge initWithStart:end:updates:]", + "coverage": 1 + }, + { + "name": "-[FRangeMerge applyToNode:]", + "coverage": 1 + }, + { + "name": "-[FRangeMerge updateRangeInNode:node:updates:]", + "coverage": 1 + }, + { + "name": "__46-[FRangeMerge updateRangeInNode:node:updates:]_block_invoke", + "coverage": 1 + }, + { + "name": "__46-[FRangeMerge updateRangeInNode:node:updates:]_block_invoke.41", + "coverage": 1 + }, + { + "name": "__46-[FRangeMerge updateRangeInNode:node:updates:]_block_invoke.45", + "coverage": 1 + }, + { + "name": "-[FRangeMerge description]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAnalyticsConfiguration.m", + "coverage": 0.9545454545454546, + "type": "objc", + "functions": [ + { + "name": "+[FIRAnalyticsConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__43+[FIRAnalyticsConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration postNotificationName:value:]", + "coverage": 0.75 + }, + { + "name": "-[FIRAnalyticsConfiguration setMinimumSessionInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setSessionTimeoutInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:persistSetting:]", + "coverage": 1 + } + ] + }, + { + "name": "FTreeSortedDictionary.m", + "coverage": 0.96415770609319, + "type": "objc", + "functions": [ + { + "name": "-[FTreeSortedDictionary initWithComparator:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary initWithComparator:withRoot:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary insertKey:withValue:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary removeKey:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary get:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary getPredecessorKey:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary isEmpty]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary count]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary minKey]", + "coverage": 0 + }, + { + "name": "-[FTreeSortedDictionary maxKey]", + "coverage": 0 + }, + { + "name": "-[FTreeSortedDictionary enumerateKeysAndObjectsUsingBlock:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary enumerateKeysAndObjectsReverse:usingBlock:]", + "coverage": 1 + }, + { + "name": "__67-[FTreeSortedDictionary enumerateKeysAndObjectsReverse:usingBlock:]_block_invoke", + "coverage": 1 + }, + { + "name": "__67-[FTreeSortedDictionary enumerateKeysAndObjectsReverse:usingBlock:]_block_invoke.60", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary contains:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary keyEnumerator]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary keyEnumeratorFrom:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary reverseKeyEnumerator]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionary reverseKeyEnumeratorFrom:]", + "coverage": 1 + }, + { + "name": "log_base2", + "coverage": 1 + }, + { + "name": "base1_2List_next", + "coverage": 1 + }, + { + "name": "bit_mask", + "coverage": 1 + }, + { + "name": "base1_2List_new", + "coverage": 1 + }, + { + "name": "base1_2List_free", + "coverage": 1 + }, + { + "name": "+[FTreeSortedDictionary buildBalancedTree:dictionary:subArrayStartIndex:length:]", + "coverage": 1 + }, + { + "name": "+[FTreeSortedDictionary rootFrom12List:keyList:dictionary:]", + "coverage": 1 + }, + { + "name": "__59+[FTreeSortedDictionary rootFrom12List:keyList:dictionary:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FTreeSortedDictionary fromDictionary:withComparator:]", + "coverage": 0.9411764705882353 + }, + { + "name": "__55+[FTreeSortedDictionary fromDictionary:withComparator:]_block_invoke", + "coverage": 1 + }, + { + "name": "__55+[FTreeSortedDictionary fromDictionary:withComparator:]_block_invoke.99", + "coverage": 0.7142857142857143 + } + ] + }, + { + "name": "FPath.m", + "coverage": 0.9644444444444444, + "type": "objc", + "functions": [ + { + "name": "+[FPath relativePathFrom:to:]", + "coverage": 0.8181818181818182 + }, + { + "name": "+[FPath pathWithString:]", + "coverage": 1 + }, + { + "name": "-[FPath initWith:]", + "coverage": 1 + }, + { + "name": "-[FPath initWithPieces:andPieceNum:]", + "coverage": 1 + }, + { + "name": "-[FPath copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[FPath description]", + "coverage": 1 + }, + { + "name": "-[FPath getFront]", + "coverage": 1 + }, + { + "name": "-[FPath length]", + "coverage": 1 + }, + { + "name": "-[FPath popFront]", + "coverage": 1 + }, + { + "name": "-[FPath getBack]", + "coverage": 1 + }, + { + "name": "-[FPath toString]", + "coverage": 1 + }, + { + "name": "-[FPath toStringWithTrailingSlash]", + "coverage": 1 + }, + { + "name": "-[FPath toStringWithTrailingSlash:]", + "coverage": 1 + }, + { + "name": "-[FPath wireFormat]", + "coverage": 1 + }, + { + "name": "-[FPath parent]", + "coverage": 1 + }, + { + "name": "-[FPath child:]", + "coverage": 1 + }, + { + "name": "-[FPath childFromString:]", + "coverage": 1 + }, + { + "name": "-[FPath isEmpty]", + "coverage": 1 + }, + { + "name": "+[FPath empty]", + "coverage": 1 + }, + { + "name": "__14+[FPath empty]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FPath contains:]", + "coverage": 1 + }, + { + "name": "-[FPath enumerateComponentsUsingBlock:]", + "coverage": 1 + }, + { + "name": "-[FPath compare:]", + "coverage": 1 + }, + { + "name": "-[FPath isEqual:]", + "coverage": 0.8888888888888888 + }, + { + "name": "-[FPath hash]", + "coverage": 1 + } + ] + }, + { + "name": "FQueryParams.m", + "coverage": 0.9703703703703703, + "type": "objc", + "functions": [ + { + "name": "+[FQueryParams defaultInstance]", + "coverage": 1 + }, + { + "name": "__31+[FQueryParams defaultInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FQueryParams init]", + "coverage": 1 + }, + { + "name": "-[FQueryParams indexStartValue]", + "coverage": 1 + }, + { + "name": "-[FQueryParams indexStartKey]", + "coverage": 1 + }, + { + "name": "-[FQueryParams indexEndValue]", + "coverage": 1 + }, + { + "name": "-[FQueryParams indexEndKey]", + "coverage": 1 + }, + { + "name": "-[FQueryParams hasAnchoredLimit]", + "coverage": 0 + }, + { + "name": "-[FQueryParams limit]", + "coverage": 1 + }, + { + "name": "-[FQueryParams hasStart]", + "coverage": 1 + }, + { + "name": "-[FQueryParams hasEnd]", + "coverage": 1 + }, + { + "name": "-[FQueryParams copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FQueryParams mutableCopy]", + "coverage": 1 + }, + { + "name": "-[FQueryParams limitTo:]", + "coverage": 1 + }, + { + "name": "-[FQueryParams limitToFirst:]", + "coverage": 1 + }, + { + "name": "-[FQueryParams limitToLast:]", + "coverage": 1 + }, + { + "name": "-[FQueryParams startAt:childKey:]", + "coverage": 1 + }, + { + "name": "-[FQueryParams startAt:]", + "coverage": 1 + }, + { + "name": "-[FQueryParams endAt:childKey:]", + "coverage": 1 + }, + { + "name": "-[FQueryParams endAt:]", + "coverage": 1 + }, + { + "name": "-[FQueryParams orderBy:]", + "coverage": 1 + }, + { + "name": "-[FQueryParams wireProtocolParams]", + "coverage": 0.9772727272727273 + }, + { + "name": "+[FQueryParams fromQueryObject:]", + "coverage": 0.95 + }, + { + "name": "-[FQueryParams isViewFromLeft]", + "coverage": 1 + }, + { + "name": "-[FQueryParams nodeFilter]", + "coverage": 1 + }, + { + "name": "-[FQueryParams isValid]", + "coverage": 1 + }, + { + "name": "-[FQueryParams loadsAllData]", + "coverage": 1 + }, + { + "name": "-[FQueryParams isDefault]", + "coverage": 1 + }, + { + "name": "-[FQueryParams description]", + "coverage": 1 + }, + { + "name": "-[FQueryParams isEqual:]", + "coverage": 0.8947368421052632 + }, + { + "name": "-[FQueryParams hash]", + "coverage": 1 + } + ] + }, + { + "name": "FSparseSnapshotTree.m", + "coverage": 0.9736842105263158, + "type": "objc", + "functions": [ + { + "name": "-[FSparseSnapshotTree init]", + "coverage": 1 + }, + { + "name": "-[FSparseSnapshotTree findPath:]", + "coverage": 1 + }, + { + "name": "-[FSparseSnapshotTree rememberData:onPath:]", + "coverage": 1 + }, + { + "name": "-[FSparseSnapshotTree forgetPath:]", + "coverage": 0.9318181818181818 + }, + { + "name": "__34-[FSparseSnapshotTree forgetPath:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSparseSnapshotTree forEachTreeAtPath:do:]", + "coverage": 1 + }, + { + "name": "__44-[FSparseSnapshotTree forEachTreeAtPath:do:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FSparseSnapshotTree forEachChild:]", + "coverage": 1 + } + ] + }, + { + "name": "FIndexedNode.m", + "coverage": 0.9736842105263158, + "type": "objc", + "functions": [ + { + "name": "+[FIndexedNode fallbackIndex]", + "coverage": 1 + }, + { + "name": "__29+[FIndexedNode fallbackIndex]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIndexedNode indexedNodeWithNode:]", + "coverage": 1 + }, + { + "name": "+[FIndexedNode indexedNodeWithNode:index:]", + "coverage": 1 + }, + { + "name": "-[FIndexedNode initWithNode:index:]", + "coverage": 1 + }, + { + "name": "-[FIndexedNode initWithNode:index:indexed:]", + "coverage": 1 + }, + { + "name": "-[FIndexedNode ensureIndexed]", + "coverage": 1 + }, + { + "name": "__29-[FIndexedNode ensureIndexed]_block_invoke", + "coverage": 1 + }, + { + "name": "__29-[FIndexedNode ensureIndexed]_block_invoke.35", + "coverage": 1 + }, + { + "name": "__29-[FIndexedNode ensureIndexed]_block_invoke.47", + "coverage": 1 + }, + { + "name": "-[FIndexedNode hasIndex:]", + "coverage": 1 + }, + { + "name": "-[FIndexedNode updateChild:withNewChild:]", + "coverage": 1 + }, + { + "name": "-[FIndexedNode updatePriority:]", + "coverage": 1 + }, + { + "name": "-[FIndexedNode firstChild]", + "coverage": 0.9166666666666666 + }, + { + "name": "-[FIndexedNode lastChild]", + "coverage": 0.9166666666666666 + }, + { + "name": "-[FIndexedNode predecessorForChildKey:childNode:index:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIndexedNode enumerateChildrenReverse:usingBlock:]", + "coverage": 1 + }, + { + "name": "__52-[FIndexedNode enumerateChildrenReverse:usingBlock:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIndexedNode childEnumerator]", + "coverage": 1 + } + ] + }, + { + "name": "FIndexedFilter.m", + "coverage": 0.9758064516129032, + "type": "objc", + "functions": [ + { + "name": "-[FIndexedFilter initWithIndex:]", + "coverage": 1 + }, + { + "name": "-[FIndexedFilter updateChildIn:forChildKey:newChild:affectedPath:fromSource:accumulator:]", + "coverage": 0.9375 + }, + { + "name": "-[FIndexedFilter updateFullNode:withNewNode:accumulator:]", + "coverage": 1 + }, + { + "name": "__57-[FIndexedFilter updateFullNode:withNewNode:accumulator:]_block_invoke", + "coverage": 1 + }, + { + "name": "__57-[FIndexedFilter updateFullNode:withNewNode:accumulator:]_block_invoke.52", + "coverage": 1 + }, + { + "name": "-[FIndexedFilter updatePriority:forNode:]", + "coverage": 1 + }, + { + "name": "-[FIndexedFilter filtersNodes]", + "coverage": 1 + }, + { + "name": "-[FIndexedFilter indexedFilter]", + "coverage": 1 + } + ] + }, + { + "name": "FArraySortedDictionary.m", + "coverage": 0.9759615384615384, + "type": "objc", + "functions": [ + { + "name": "-[FArraySortedDictionaryEnumerator initWithKeys:startPos:isReverse:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionaryEnumerator nextObject]", + "coverage": 1 + }, + { + "name": "+[FArraySortedDictionary fromDictionary:withComparator:]", + "coverage": 1 + }, + { + "name": "__56+[FArraySortedDictionary fromDictionary:withComparator:]_block_invoke", + "coverage": 1 + }, + { + "name": "__56+[FArraySortedDictionary fromDictionary:withComparator:]_block_invoke.43", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FArraySortedDictionary initWithComparator:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary initWithComparator:keys:values:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary findInsertPositionForKey:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary findKey:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary insertKey:withValue:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary removeKey:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary get:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary getPredecessorKey:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary isEmpty]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary count]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary minKey]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary maxKey]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary enumerateKeysAndObjectsUsingBlock:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary enumerateKeysAndObjectsReverse:usingBlock:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary contains:]", + "coverage": 0 + }, + { + "name": "-[FArraySortedDictionary keyEnumerator]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary keyEnumeratorFrom:]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary reverseKeyEnumerator]", + "coverage": 1 + }, + { + "name": "-[FArraySortedDictionary reverseKeyEnumeratorFrom:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentContainer.m", + "coverage": 0.9818181818181818, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:]", + "coverage": 1 + }, + { + "name": "__55+[FIRComponentContainer registerAsComponentRegistrant:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:inSet:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:registrants:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer populateComponentsFromRegisteredClasses:forApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer instantiateInstanceForProtocol:withBlock:]", + "coverage": 0.9285714285714286 + }, + { + "name": "-[FIRComponentContainer instanceForProtocol:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer removeAllCachedInstances]", + "coverage": 1 + } + ] + }, + { + "name": "FCompoundHash.m", + "coverage": 0.9883040935672515, + "type": "objc", + "functions": [ + { + "name": "-[FCompoundHashBuilder initWithSplitStrategy:]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder isBuildingRange]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder currentHashLength]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder currentPath]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder currentPathWithDepth:]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder enumerateCurrentPathToDepth:withBlock:]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder appendKey:toString:]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder ensureRange]", + "coverage": 1 + }, + { + "name": "__35-[FCompoundHashBuilder ensureRange]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder processLeaf:]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder startChild:]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder endChild]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder finishHashing]", + "coverage": 1 + }, + { + "name": "-[FCompoundHashBuilder endRange]", + "coverage": 1 + }, + { + "name": "-[FCompoundHash initWithPosts:hashes:]", + "coverage": 0.8181818181818182 + }, + { + "name": "+[FCompoundHash simpleSizeSplitStrategyForNode:]", + "coverage": 1 + }, + { + "name": "__48+[FCompoundHash simpleSizeSplitStrategyForNode:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FCompoundHash fromNode:]", + "coverage": 1 + }, + { + "name": "+[FCompoundHash fromNode:splitStrategy:]", + "coverage": 1 + }, + { + "name": "+[FCompoundHash processNode:builder:]", + "coverage": 1 + }, + { + "name": "__37+[FCompoundHash processNode:builder:]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "main.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "main", + "coverage": 1 + } + ] + }, + { + "name": "FTransformedEnumerator.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FTransformedEnumerator initWithEnumerator:andTransform:]", + "coverage": 1 + }, + { + "name": "-[FTransformedEnumerator nextObject]", + "coverage": 1 + } + ] + }, + { + "name": "FClock.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FSystemClock currentTime]", + "coverage": 1 + }, + { + "name": "+[FSystemClock clock]", + "coverage": 1 + }, + { + "name": "__21+[FSystemClock clock]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FOffsetClock currentTime]", + "coverage": 1 + }, + { + "name": "-[FOffsetClock initWithClock:offset:]", + "coverage": 1 + } + ] + }, + { + "name": "FEmptyNode.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FEmptyNode emptyNode]", + "coverage": 1 + }, + { + "name": "__23+[FEmptyNode emptyNode]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FViewCache.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FViewCache initWithEventCache:serverCache:]", + "coverage": 1 + }, + { + "name": "-[FViewCache updateEventSnap:isComplete:isFiltered:]", + "coverage": 1 + }, + { + "name": "-[FViewCache updateServerSnap:isComplete:isFiltered:]", + "coverage": 1 + }, + { + "name": "-[FViewCache completeEventSnap]", + "coverage": 1 + }, + { + "name": "-[FViewCache completeServerSnap]", + "coverage": 1 + } + ] + }, + { + "name": "FAtomicNumber.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FAtomicNumber init]", + "coverage": 1 + }, + { + "name": "-[FAtomicNumber getAndIncrement]", + "coverage": 1 + } + ] + }, + { + "name": "FCacheNode.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FCacheNode initWithIndexedNode:isFullyInitialized:isFiltered:]", + "coverage": 1 + }, + { + "name": "-[FCacheNode isCompleteForPath:]", + "coverage": 1 + }, + { + "name": "-[FCacheNode isCompleteForChild:]", + "coverage": 1 + }, + { + "name": "-[FCacheNode node]", + "coverage": 1 + } + ] + }, + { + "name": "FTreeNode.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FTreeNode init]", + "coverage": 1 + } + ] + }, + { + "name": "FWriteTreeRef.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FWriteTreeRef initWithPath:writeTree:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeRef calculateCompleteEventCacheWithCompleteServerCache:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeRef calculateCompleteEventChildrenWithCompleteServerChildren:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeRef calculateEventCacheAfterServerOverwriteWithChildPath:existingEventSnap:existingServerSnap:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeRef shadowingWriteAtPath:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeRef calculateNextNodeAfterPost:completeServerData:reverse:index:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeRef calculateCompleteChild:cache:]", + "coverage": 1 + }, + { + "name": "-[FWriteTreeRef childWriteTreeRef:]", + "coverage": 1 + } + ] + }, + { + "name": "FSnapshotHolder.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FSnapshotHolder init]", + "coverage": 1 + }, + { + "name": "-[FSnapshotHolder getNode:]", + "coverage": 1 + }, + { + "name": "-[FSnapshotHolder updateSnapshot:withNewSnapshot:]", + "coverage": 1 + } + ] + }, + { + "name": "FTuplePathValue.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FTuplePathValue initWithPath:value:]", + "coverage": 1 + } + ] + }, + { + "name": "FTupleRemovedQueriesEvents.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FTupleRemovedQueriesEvents initWithRemovedQueries:cancelEvents:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentType.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentType instanceForProtocol:inContainer:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponent.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponent componentWithProtocol:creationBlock:]", + "coverage": 1 + }, + { + "name": "+[FIRComponent componentWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + }, + { + "name": "-[FIRComponent initWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAppAssociationRegistration.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRAppAssociationRegistration registeredObjectWithHost:key:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FTreeSortedDictionaryEnumerator.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FTreeSortedDictionaryEnumerator initWithImmutableSortedDictionary:startKey:isReverse:]", + "coverage": 1 + }, + { + "name": "-[FTreeSortedDictionaryEnumerator nextObject]", + "coverage": 1 + } + ] + }, + { + "name": "slice.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::Slice::Slice()", + "coverage": 1 + }, + { + "name": "leveldb::Slice::Slice(char const*, unsigned long)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::Slice(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::Slice(char const*)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::data() const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::size() const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::empty() const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::operator[](unsigned long) const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::clear()", + "coverage": 1 + }, + { + "name": "leveldb::Slice::remove_prefix(unsigned long)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::ToString() const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::starts_with(leveldb::Slice const&) const", + "coverage": 1 + }, + { + "name": "leveldb::operator==(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::operator!=(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::compare(leveldb::Slice const&) const", + "coverage": 1 + } + ] + }, + { + "name": "options.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::ReadOptions::ReadOptions()", + "coverage": 1 + }, + { + "name": "leveldb::WriteOptions::WriteOptions()", + "coverage": 1 + } + ] + }, + { + "name": "FViewProcessorResult.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FViewProcessorResult initWithViewCache:changes:]", + "coverage": 1 + } + ] + } + ] + }, + { + "name": "DynamicLinks_Example_iOS.app", + "coverage": 0.7375878853434289, + "files": [ + { + "name": "GINArgument.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[GINArgument argumentWithObject:]", + "coverage": 0 + }, + { + "name": "+[GINArgument argumentWithInteger:]", + "coverage": 0 + }, + { + "name": "+[GINArgument setNextArgumentInList:atIndex:inInvocation:]", + "coverage": 0 + }, + { + "name": "-[GINArgument setArgumentInInvocation:atIndex:]", + "coverage": 0 + } + ] + }, + { + "name": "GINInvocation.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[GINInvocation objectByPerformingSelector:onTarget:numberOfArguments:]", + "coverage": 0 + }, + { + "name": "+[GINInvocation doubleByPerformingSelector:onTarget:numberOfArguments:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDLDefaultRetrievalProcessV2.m", + "coverage": 0.11191335740072202, + "type": "objc", + "functions": [ + { + "name": "-[FIRDLDefaultRetrievalProcessV2 initWithNetworkingService:clientID:URLScheme:APIKey:FDLSDKVersion:delegate:]", + "coverage": 1 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 retrievePendingDynamicLink]", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 isCompleted]", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 javaScriptExecutor:completedExecutionWithResult:]", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 javaScriptExecutor:failedWithError:]", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 retrievePendingDynamicLinkInternal]", + "coverage": 0 + }, + { + "name": "__68-[FIRDLDefaultRetrievalProcessV2 retrievePendingDynamicLinkInternal]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 foundResultsWithDynamicLinks]", + "coverage": 0 + }, + { + "name": "__62-[FIRDLDefaultRetrievalProcessV2 foundResultsWithDynamicLinks]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 resultsWithErrors]", + "coverage": 0 + }, + { + "name": "__51-[FIRDLDefaultRetrievalProcessV2 resultsWithErrors]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 results]", + "coverage": 0 + }, + { + "name": "__41-[FIRDLDefaultRetrievalProcessV2 results]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 resultWithUniqueMatchedDynamicLink]", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 handleRequestResultsUpdated]", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 markCompleted]", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 uniqueMatchLinkToCheck]", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 clearUsedUniqueMatchLinkToCheckFromClipboard]", + "coverage": 0 + }, + { + "name": "-[FIRDLDefaultRetrievalProcessV2 fetchLocaleFromWebView]", + "coverage": 0.8 + } + ] + }, + { + "name": "FIRInstanceIDTokenDeleteOperation.m", + "coverage": 0.12643678160919541, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenDeleteOperation initWithAuthorizedEntity:scope:checkinPreferences:keyPair:action:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenDeleteOperation performTokenOperation]", + "coverage": 0 + }, + { + "name": "__58-[FIRInstanceIDTokenDeleteOperation performTokenOperation]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenDeleteOperation handleResponseWithData:response:error:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDLJavaScriptExecutor.m", + "coverage": 0.1736111111111111, + "type": "objc", + "functions": [ + { + "name": "FIRDLTypeofFingerprintJSMethodNameString", + "coverage": 0 + }, + { + "name": "__FIRDLTypeofFingerprintJSMethodNameString_block_invoke", + "coverage": 0 + }, + { + "name": "GINFingerprintJSMethodString", + "coverage": 0 + }, + { + "name": "__GINFingerprintJSMethodString_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDLJavaScriptExecutor initWithDelegate:script:]", + "coverage": 1 + }, + { + "name": "-[FIRDLJavaScriptExecutor start]", + "coverage": 0.75 + }, + { + "name": "-[FIRDLJavaScriptExecutor handleExecutionResult:]", + "coverage": 0 + }, + { + "name": "-[FIRDLJavaScriptExecutor handleExecutionError:]", + "coverage": 0 + }, + { + "name": "-[FIRDLJavaScriptExecutor cleanup]", + "coverage": 0 + }, + { + "name": "-[FIRDLJavaScriptExecutor webView:didFinishNavigation:]", + "coverage": 0 + }, + { + "name": "__55-[FIRDLJavaScriptExecutor webView:didFinishNavigation:]_block_invoke", + "coverage": 0 + }, + { + "name": "__55-[FIRDLJavaScriptExecutor webView:didFinishNavigation:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRDLJavaScriptExecutor webView:didFailNavigation:withError:]", + "coverage": 0 + }, + { + "name": "-[FIRDLJavaScriptExecutor webViewDidFinishLoad:]", + "coverage": 0 + }, + { + "name": "-[FIRDLJavaScriptExecutor webView:didFailLoadWithError:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAppDelegate.m", + "coverage": 0.2413793103448276, + "type": "objc", + "functions": [ + { + "name": "-[FIRAppDelegate application:didFinishLaunchingWithOptions:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillResignActive:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidEnterBackground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationWillEnterForeground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidBecomeActive:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillTerminate:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDynamicLinkNetworking.m", + "coverage": 0.30670103092783507, + "type": "objc", + "functions": [ + { + "name": "FIRURLParameterString", + "coverage": 0.6666666666666666 + }, + { + "name": "FIRDynamicLinkAPIKeyParameter", + "coverage": 1 + }, + { + "name": "FIRMakeHTTPRequest", + "coverage": 1 + }, + { + "name": "__FIRMakeHTTPRequest_block_invoke", + "coverage": 0 + }, + { + "name": "FIRDataWithDictionary", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkNetworking initWithAPIKey:clientID:URLScheme:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkNetworking resolveShortLink:FDLSDKVersion:completion:]", + "coverage": 0.9387755102040817 + }, + { + "name": "__70-[FIRDynamicLinkNetworking resolveShortLink:FDLSDKVersion:completion:]_block_invoke", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRDynamicLinkNetworking retrievePendingDynamicLinkWithIOSVersion:resolutionHeight:resolutionWidth:locale:localeRaw:localeFromWebView:timezone:modelName:FDLSDKVersion:appInstallationDate:uniqueMatchVisualStyle:retrievalProcessType:uniqueMatchLinkToCheck:handler:]", + "coverage": 0 + }, + { + "name": "__265-[FIRDynamicLinkNetworking retrievePendingDynamicLinkWithIOSVersion:resolutionHeight:resolutionWidth:locale:localeRaw:localeFromWebView:timezone:modelName:FDLSDKVersion:appInstallationDate:uniqueMatchVisualStyle:retrievalProcessType:uniqueMatchLinkToCheck:handler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinkNetworking convertInvitation:handler:]", + "coverage": 0 + }, + { + "name": "__54-[FIRDynamicLinkNetworking convertInvitation:handler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__54-[FIRDynamicLinkNetworking convertInvitation:handler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinkNetworking sendRequestWithBaseURLString:requestBody:endpointPath:parserBlock:completion:]", + "coverage": 0 + }, + { + "name": "__105-[FIRDynamicLinkNetworking sendRequestWithBaseURLString:requestBody:endpointPath:parserBlock:completion:]_block_invoke", + "coverage": 0 + }, + { + "name": "__105-[FIRDynamicLinkNetworking sendRequestWithBaseURLString:requestBody:endpointPath:parserBlock:completion:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__105-[FIRDynamicLinkNetworking sendRequestWithBaseURLString:requestBody:endpointPath:parserBlock:completion:]_block_invoke.201", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinkNetworking executeOnePlatformRequest:forURL:completionHandler:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceID+Private.m", + "coverage": 0.3333333333333333, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceID(Private) cachedCheckinPreferences]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID(Private) fetchCheckinInfoWithHandler:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID(Private) appInstanceID:]", + "coverage": 1 + } + ] + }, + { + "name": "NSError+FIRInstanceID.m", + "coverage": 0.375, + "type": "objc", + "functions": [ + { + "name": "-[NSError(FIRInstanceID) instanceIDErrorCode]", + "coverage": 0 + }, + { + "name": "+[NSError(FIRInstanceID) errorWithFIRInstanceIDErrorCode:]", + "coverage": 1 + }, + { + "name": "+[NSError(FIRInstanceID) errorWithFIRInstanceIDErrorCode:userInfo:]", + "coverage": 1 + }, + { + "name": "+[NSError(FIRInstanceID) FIRInstanceIDErrorMissingCheckin]", + "coverage": 0 + } + ] + }, + { + "name": "FIRViewController.m", + "coverage": 0.5, + "type": "objc", + "functions": [ + { + "name": "-[FIRViewController viewDidLoad]", + "coverage": 1 + }, + { + "name": "-[FIRViewController didReceiveMemoryWarning]", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDStringEncoding.m", + "coverage": 0.5308641975308642, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDStringEncoding rfc4648Base64WebsafeStringEncoding]", + "coverage": 1 + }, + { + "name": "lcm", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStringEncoding stringEncodingWithString:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStringEncoding initWithString:]", + "coverage": 0.6896551724137931 + }, + { + "name": "-[FIRInstanceIDStringEncoding description]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDStringEncoding doPad]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDStringEncoding setDoPad:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStringEncoding setPaddingChar:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStringEncoding encode:]", + "coverage": 0.8444444444444444 + }, + { + "name": "-[FIRInstanceIDStringEncoding decode:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDynamicLinkComponentsKeyProvider.m", + "coverage": 0.6, + "type": "objc", + "functions": [ + { + "name": "+[FIRDynamicLinkComponentsKeyProvider APIKey]", + "coverage": 0.6 + } + ] + }, + { + "name": "FIRDynamicLinks.m", + "coverage": 0.6314878892733564, + "type": "objc", + "functions": [ + { + "name": "+[FIRDynamicLinks load]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinks componentsToRegister]", + "coverage": 1 + }, + { + "name": "__39+[FIRDynamicLinks componentsToRegister]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinks configureWithApp:]", + "coverage": 0.6 + }, + { + "name": "-[FIRDynamicLinks configureDynamicLinks:]", + "coverage": 0.391304347826087 + }, + { + "name": "-[FIRDynamicLinks initWithAnalytics:]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinks dynamicLinks]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinks setUpWithLaunchOptions:apiKey:clientID:urlScheme:userDefaults:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinks checkForPendingDynamicLinkUsingExperimentalRetrievalProcess]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks checkForPendingDynamicLink]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinks sharedInstance]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks checkForPendingDeepLink]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks deepLinkFromCustomSchemeURL:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks deepLinkFromUniversalLinkURL:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks shouldHandleDeepLinkFromCustomSchemeURL:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks shouldHandleDynamicLinkFromCustomSchemeURL:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks dynamicLinkFromCustomSchemeURL:]", + "coverage": 0.9444444444444444 + }, + { + "name": "-[FIRDynamicLinks dynamicLinkFromUniversalLinkURL:]", + "coverage": 1 + }, + { + "name": "__51-[FIRDynamicLinks dynamicLinkFromUniversalLinkURL:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinks handleUniversalLink:completion:]", + "coverage": 1 + }, + { + "name": "__50-[FIRDynamicLinks handleUniversalLink:completion:]_block_invoke", + "coverage": 0.8181818181818182 + }, + { + "name": "__50-[FIRDynamicLinks handleUniversalLink:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinks resolveShortLink:completion:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinks matchesShortLinkFormat:]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinks isAutomaticRetrievalEnabled]", + "coverage": 0.75 + }, + { + "name": "-[FIRDynamicLinks dynamicLinkNetworking]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinks canParseCustomSchemeURL:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinks canParseUniversalLinkURL:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinks handleIncomingCustomSchemeDeepLink:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks passRetrievedDynamicLinkToApplication:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks handlePendingDynamicLinkRetrievalFailureWithErrorCode:errorDescription:underlyingError:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLinks retrievalProcess:completedWithResult:]", + "coverage": 0 + }, + { + "name": "+[FIRDynamicLinks genericDiagnosticInformation]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinks diagnosticAnalyzeEntitlements]", + "coverage": 0 + }, + { + "name": "+[FIRDynamicLinks performDiagnosticsIncludingHeaderFooter:detectedErrors:]", + "coverage": 0.7671232876712328 + }, + { + "name": "+[FIRDynamicLinks performDiagnosticsWithCompletion:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDTokenManager.m", + "coverage": 0.7067039106145251, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenManager init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager configureTokenOperations]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke.52", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke_2.53", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager cachedTokenInfoWithAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager deleteTokenWithAuthorizedEntity:scope:keyPair:handler:]", + "coverage": 0.9166666666666666 + }, + { + "name": "__83-[FIRInstanceIDTokenManager deleteTokenWithAuthorizedEntity:scope:keyPair:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__83-[FIRInstanceIDTokenManager deleteTokenWithAuthorizedEntity:scope:keyPair:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]", + "coverage": 0 + }, + { + "name": "__64-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__64-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]_block_invoke.86", + "coverage": 0 + }, + { + "name": "__64-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager deleteAllTokensLocallyWithHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager stopAllTokenOperations]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager store:didDeleteFCMScopedTokensForCheckin:]", + "coverage": 0 + }, + { + "name": "__70-[FIRInstanceIDTokenManager store:didDeleteFCMScopedTokensForCheckin:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager createFetchOperationWithAuthorizedEntity:scope:options:keyPair:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager createDeleteOperationWithAuthorizedEntity:scope:checkinPreferences:keyPair:action:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager checkForTokenRefreshPolicy]", + "coverage": 0.8125 + }, + { + "name": "-[FIRInstanceIDTokenManager updateTokensToAPNSDeviceToken:isSandbox:]", + "coverage": 0.9333333333333333 + } + ] + }, + { + "name": "FIRInstanceIDLogger.m", + "coverage": 0.72, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDLogger formatMessageCode:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDLogger logFuncDebug:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDLogger logFuncInfo:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDLogger logFuncNotice:messageCode:msg:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDLogger logFuncWarning:messageCode:msg:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDLogger logFuncError:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "FIRInstanceIDSharedLogger", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDSharedLogger_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceID.m", + "coverage": 0.7266414141414141, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDResult copyWithZone:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID instanceID]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIRInstanceID initPrivately]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID instanceIDForTests]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID token]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID instanceIDWithHandler:]", + "coverage": 1 + }, + { + "name": "__39-[FIRInstanceID instanceIDWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__39-[FIRInstanceID instanceIDWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID cachedTokenIfAvailable]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID setDefaultFCMToken:]", + "coverage": 0.8666666666666667 + }, + { + "name": "-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]", + "coverage": 0.8584905660377359 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke.163", + "coverage": 0.9302325581395349 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke_2.171", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]", + "coverage": 0.84375 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke", + "coverage": 0.8 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke.195", + "coverage": 0.8695652173913043 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke_2.196", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID asyncLoadKeyPairWithHandler:]", + "coverage": 1 + }, + { + "name": "__45-[FIRInstanceID asyncLoadKeyPairWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__45-[FIRInstanceID asyncLoadKeyPairWithHandler:]_block_invoke_2", + "coverage": 0.7222222222222222 + }, + { + "name": "-[FIRInstanceID getIDWithHandler:]", + "coverage": 0.7837837837837838 + }, + { + "name": "__34-[FIRInstanceID getIDWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__34-[FIRInstanceID getIDWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__34-[FIRInstanceID getIDWithHandler:]_block_invoke.223", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID deleteIDWithHandler:]", + "coverage": 0.8333333333333334 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke", + "coverage": 0.5555555555555556 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke.240", + "coverage": 0 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke_2.241", + "coverage": 0 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke.250", + "coverage": 0.8 + }, + { + "name": "-[FIRInstanceID notifyIdentityReset]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID deleteIdentityWithHandler:]", + "coverage": 1 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke", + "coverage": 0.9137931034482759 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke_3", + "coverage": 0 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke_4", + "coverage": 0 + }, + { + "name": "+[FIRInstanceID load]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID componentsToRegister]", + "coverage": 1 + }, + { + "name": "__37+[FIRInstanceID componentsToRegister]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID configureWithApp:]", + "coverage": 0.4444444444444444 + }, + { + "name": "-[FIRInstanceID configureInstanceIDWithOptions:app:]", + "coverage": 0.7222222222222222 + }, + { + "name": "+[FIRInstanceID configureErrorWithReason:]", + "coverage": 0 + }, + { + "name": "+[FIRInstanceID exitWithReason:forFirebaseApp:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID didCompleteConfigure]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID isFCMAutoInitEnabled]", + "coverage": 0.8709677419354839 + }, + { + "name": "-[FIRInstanceID start]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FIRInstanceID setupTokenManager]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID setupKeyPairManager]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID setupNotificationListeners]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID maxRetryCountForDefaultToken]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID minIntervalForDefaultTokenRetry]", + "coverage": 0 + }, + { + "name": "+[FIRInstanceID maxRetryIntervalForDefaultTokenInSeconds]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID retryIntervalToFetchDefaultToken]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIRInstanceID fetchDefaultToken]", + "coverage": 1 + }, + { + "name": "__34-[FIRInstanceID fetchDefaultToken]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID defaultTokenWithHandler:]", + "coverage": 0.89 + }, + { + "name": "__41-[FIRInstanceID defaultTokenWithHandler:]_block_invoke", + "coverage": 0.8133333333333334 + }, + { + "name": "__41-[FIRInstanceID defaultTokenWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__41-[FIRInstanceID defaultTokenWithHandler:]_block_invoke.406", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID notifyAPNSTokenIsSet:]", + "coverage": 0 + }, + { + "name": "__38-[FIRInstanceID notifyAPNSTokenIsSet:]_block_invoke", + "coverage": 0 + }, + { + "name": "__38-[FIRInstanceID notifyAPNSTokenIsSet:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID isSandboxApp]", + "coverage": 1 + }, + { + "name": "__29-[FIRInstanceID isSandboxApp]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID isProductionApp]", + "coverage": 0.22556390977443608 + }, + { + "name": "-[FIRInstanceID logAPNSConfigurationError:]", + "coverage": 0.875 + } + ] + }, + { + "name": "FDLUtilities.m", + "coverage": 0.7358490566037735, + "type": "objc", + "functions": [ + { + "name": "FIRDLCookieRetrievalURL", + "coverage": 1 + }, + { + "name": "FIRDLURLQueryStringFromDictionary", + "coverage": 1 + }, + { + "name": "__FIRDLURLQueryStringFromDictionary_block_invoke", + "coverage": 1 + }, + { + "name": "FIRDLDictionaryFromQuery", + "coverage": 1 + }, + { + "name": "FIRDLDeepLinkURLWithInviteID", + "coverage": 0.926829268292683 + }, + { + "name": "FIRDLOSVersionSupported", + "coverage": 1 + }, + { + "name": "FIRDLAppInstallationDate", + "coverage": 0 + }, + { + "name": "FIRDLDeviceModelName", + "coverage": 0 + }, + { + "name": "__FIRDLDeviceModelName_block_invoke", + "coverage": 0 + }, + { + "name": "FIRDLDeviceLocale", + "coverage": 1 + }, + { + "name": "FIRDLDeviceLocaleRaw", + "coverage": 1 + }, + { + "name": "FIRDLDeviceTimezone", + "coverage": 1 + }, + { + "name": "FIRDLIsURLForWhiteListedCustomDomain", + "coverage": 1 + }, + { + "name": "FIRDLCanParseUniversalLinkURL", + "coverage": 1 + }, + { + "name": "FIRDLMatchesShortLinkFormat", + "coverage": 1 + }, + { + "name": "FIRDLMatchTypeStringFromServerString", + "coverage": 0 + }, + { + "name": "__FIRDLMatchTypeStringFromServerString_block_invoke", + "coverage": 0 + }, + { + "name": "FIRDLAddToAllowListForCustomDomainsArray", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDTokenFetchOperation.m", + "coverage": 0.75, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenFetchOperation initWithAuthorizedEntity:scope:options:checkinPreferences:keyPair:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenFetchOperation performTokenOperation]", + "coverage": 0.9206349206349206 + }, + { + "name": "__57-[FIRInstanceIDTokenFetchOperation performTokenOperation]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenFetchOperation handleResponseWithData:response:error:]", + "coverage": 0.5454545454545454 + }, + { + "name": "__74-[FIRInstanceIDTokenFetchOperation handleResponseWithData:response:error:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenFetchOperation parseFetchTokenResponse:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDVersionUtilities.m", + "coverage": 0.775, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDParseCurrentLibraryVersion", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDParseCurrentLibraryVersion_block_invoke", + "coverage": 0.9285714285714286 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersion", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionMajor", + "coverage": 0 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionMinor", + "coverage": 0 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionPatch", + "coverage": 0 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionIsBeta", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDURLQueryItem.m", + "coverage": 0.78125, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDURLQueryItem queryItemWithName:value:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDURLQueryItem initWithName:value:]", + "coverage": 1 + }, + { + "name": "FIRInstanceIDQueryFromQueryItems", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "FIRInstanceIDBackupExcludedPlist.m", + "coverage": 0.7816901408450704, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDBackupExcludedPlist initWithFileName:subDirectory:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist writeDictionary:error:]", + "coverage": 0.696969696969697 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist deleteFile:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist contentAsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist moveToApplicationSupportSubDirectory:]", + "coverage": 0.5428571428571428 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist doesFileExist]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist plistDirectory]", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist plistPathInDirectory:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist pathWithName:inDirectory:]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist doesFileExistInDirectory:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist supportedDirectory]", + "coverage": 1 + } + ] + }, + { + "name": "FIRDependency.m", + "coverage": 0.7857142857142857, + "type": "objc", + "functions": [ + { + "name": "+[FIRDependency dependencyWithProtocol:]", + "coverage": 0 + }, + { + "name": "+[FIRDependency dependencyWithProtocol:isRequired:]", + "coverage": 1 + }, + { + "name": "-[FIRDependency initWithProtocol:isRequired:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRBundleUtil.m", + "coverage": 0.7872340425531915, + "type": "objc", + "functions": [ + { + "name": "+[FIRBundleUtil relevantBundles]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil optionsDictionaryPathWithResourceName:andFileType:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil relevantURLSchemes]", + "coverage": 0 + }, + { + "name": "+[FIRBundleUtil hasBundleIdentifierPrefix:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil bundleIdentifierByRemovingLastPartFrom:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinStore.m", + "coverage": 0.7874396135265701, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDCheckinStore initWithCheckinPlistFileName:subDirectoryName:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore initWithCheckinPlist:keychain:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore hasCheckinPlist]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore bundleIdentifierForKeychainAccount]", + "coverage": 1 + }, + { + "name": "__63-[FIRInstanceIDCheckinStore bundleIdentifierForKeychainAccount]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore saveCheckinPreferences:handler:]", + "coverage": 0.5573770491803278 + }, + { + "name": "__60-[FIRInstanceIDCheckinStore saveCheckinPreferences:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore removeCheckinPreferencesWithHandler:]", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceIDCheckinStore removeCheckinPreferencesWithHandler:]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRInstanceIDCheckinStore cachedCheckinPreferences]", + "coverage": 0.7073170731707317 + }, + { + "name": "-[FIRInstanceIDCheckinStore migrateCheckinItemIfNeeded]", + "coverage": 1 + } + ] + }, + { + "name": "FIRDynamicLink.m", + "coverage": 0.8, + "type": "objc", + "functions": [ + { + "name": "-[FIRDynamicLink description]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLink initWithParametersDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLink setUrl:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLink setMinimumAppVersion:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLink setInviteId:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLink setWeakMatchEndpoint:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLink setMatchType:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLink setMatchMessage:]", + "coverage": 0 + }, + { + "name": "-[FIRDynamicLink setParametersDictionaryValue:forKey:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLink matchConfidence]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLink stringWithMatchType:]", + "coverage": 0.8333333333333334 + }, + { + "name": "+[FIRDynamicLink matchTypeWithString:]", + "coverage": 1 + }, + { + "name": "__38+[FIRDynamicLink matchTypeWithString:]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FDLLogging.m", + "coverage": 0.8064516129032258, + "type": "objc", + "functions": [ + { + "name": "FDLMessageCodeForLogIdentifier", + "coverage": 1 + }, + { + "name": "FDLLog", + "coverage": 0.7692307692307693 + } + ] + }, + { + "name": "FIRConfiguration.m", + "coverage": 0.8076923076923077, + "type": "objc", + "functions": [ + { + "name": "+[FIRConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__34+[FIRConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration init]", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration setLoggerLevel:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDLRetrievalProcessResult.m", + "coverage": 0.8076923076923077, + "type": "objc", + "functions": [ + { + "name": "-[FIRDLRetrievalProcessResult initWithDynamicLink:error:message:matchSource:]", + "coverage": 1 + }, + { + "name": "-[FIRDLRetrievalProcessResult URLWithCustomURLScheme:]", + "coverage": 0.7058823529411765 + } + ] + }, + { + "name": "FIRLogger.m", + "coverage": 0.8118811881188119, + "type": "objc", + "functions": [ + { + "name": "FIRLoggerInitializeASL", + "coverage": 1 + }, + { + "name": "__FIRLoggerInitializeASL_block_invoke", + "coverage": 0.9333333333333333 + }, + { + "name": "FIRSetAnalyticsDebugMode", + "coverage": 0 + }, + { + "name": "FIRSetLoggerLevel", + "coverage": 0 + }, + { + "name": "FIRResetLogger", + "coverage": 1 + }, + { + "name": "FIRSetLoggerUserDefaults", + "coverage": 1 + }, + { + "name": "FIRIsLoggableLevel", + "coverage": 0 + }, + { + "name": "FIRLogBasic", + "coverage": 1 + }, + { + "name": "Definition at 158:95", + "coverage": 1 + }, + { + "name": "+[FIRLoggerWrapper logWithLevel:withService:withCode:withMessage:withArgs:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDTokenOperation.m", + "coverage": 0.8323353293413174, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDTokenOperation sharedURLSession]", + "coverage": 0 + }, + { + "name": "__47+[FIRInstanceIDTokenOperation sharedURLSession]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenOperation initWithAction:forAuthorizedEntity:scope:options:checkinPreferences:keyPair:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation addCompletionHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation isAsynchronous]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenOperation isExecuting]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation setExecuting:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation isFinished]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation setFinished:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation start]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FIRInstanceIDTokenOperation finishWithResult:token:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation cancel]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation performTokenOperation]", + "coverage": 0 + }, + { + "name": "+[FIRInstanceIDTokenOperation requestWithAuthHeader:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenOperation standardQueryItemsWithDeviceID:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation queryItemsWithKeyPair:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDKeyPairStore.m", + "coverage": 0.8351449275362319, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDKeyDataWithTag", + "coverage": 0.8333333333333334 + }, + { + "name": "FIRInstanceIDCachedKeyRefWithTag", + "coverage": 0.7777777777777778 + }, + { + "name": "FIRInstanceIDHasMigratedKeyPair", + "coverage": 1 + }, + { + "name": "FIRInstanceIDLegacyPublicTagWithSubtype", + "coverage": 1 + }, + { + "name": "FIRInstanceIDLegacyPrivateTagWithSubtype", + "coverage": 1 + }, + { + "name": "FIRInstanceIDPublicTagWithSubtype", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDPublicTagWithSubtype_block_invoke", + "coverage": 1 + }, + { + "name": "FIRInstanceIDPrivateTagWithSubtype", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDPrivateTagWithSubtype_block_invoke", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCreationTimeKeyWithSubtype", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore invalidateKeyPairsIfNeeded]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore hasCachedKeyPairs]", + "coverage": 0.29411764705882354 + }, + { + "name": "-[FIRInstanceIDKeyPairStore appIdentityWithError:]", + "coverage": 0.6 + }, + { + "name": "-[FIRInstanceIDKeyPairStore loadKeyPairWithError:]", + "coverage": 0.78125 + }, + { + "name": "-[FIRInstanceIDKeyPairStore generateAndSaveKeyWithSubtype:creationTime:error:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRInstanceIDKeyPairStore validCachedKeyPairWithSubtype:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore cachedKeyPairWithSubtype:error:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDKeyPairStore keyPairForPrivateKeyTag:publicKeyTag:error:]", + "coverage": 0.8387096774193549 + }, + { + "name": "-[FIRInstanceIDKeyPairStore migrateKeyPairCacheIfNeededWithHandler:]", + "coverage": 0.8771929824561403 + }, + { + "name": "__68-[FIRInstanceIDKeyPairStore migrateKeyPairCacheIfNeededWithHandler:]_block_invoke", + "coverage": 0.8636363636363636 + }, + { + "name": "__68-[FIRInstanceIDKeyPairStore migrateKeyPairCacheIfNeededWithHandler:]_block_invoke_2", + "coverage": 0.6428571428571429 + }, + { + "name": "-[FIRInstanceIDKeyPairStore updateKeyRef:withTag:handler:]", + "coverage": 1 + }, + { + "name": "__58-[FIRInstanceIDKeyPairStore updateKeyRef:withTag:handler:]_block_invoke", + "coverage": 0.7619047619047619 + }, + { + "name": "__58-[FIRInstanceIDKeyPairStore updateKeyRef:withTag:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore deleteSavedKeyPairWithSubtype:handler:]", + "coverage": 0.9375 + }, + { + "name": "__67-[FIRInstanceIDKeyPairStore deleteSavedKeyPairWithSubtype:handler:]_block_invoke", + "coverage": 0.76 + }, + { + "name": "+[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:publicTag:handler:]", + "coverage": 1 + }, + { + "name": "__75+[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:publicTag:handler:]_block_invoke", + "coverage": 0.7619047619047619 + }, + { + "name": "__75+[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:publicTag:handler:]_block_invoke_2", + "coverage": 0.5454545454545454 + }, + { + "name": "-[FIRInstanceIDKeyPairStore removeKeyPairCreationTimePlistWithError:]", + "coverage": 0.5 + }, + { + "name": "+[FIRInstanceIDKeyPairStore keyStoreFileName]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDUtilities.m", + "coverage": 0.8547008547008547, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDRegisterServer", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentTimestampInSeconds", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentTimestampInMilliseconds", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentAppVersion", + "coverage": 1 + }, + { + "name": "FIRInstanceIDAppIdentifier", + "coverage": 1 + }, + { + "name": "FIRInstanceIDFirebaseAppID", + "coverage": 1 + }, + { + "name": "FIRInstanceIDDeviceModel", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDDeviceModel_block_invoke", + "coverage": 1 + }, + { + "name": "FIRInstanceIDOperatingSystemVersion", + "coverage": 1 + }, + { + "name": "FIRInstanceIDHasLocaleChanged", + "coverage": 1 + }, + { + "name": "FIRInstanceIDIsValidGCMScope", + "coverage": 1 + }, + { + "name": "FIRInstanceIDStringForAPNSDeviceToken", + "coverage": 1 + }, + { + "name": "FIRInstanceIDAPNSTupleStringForTokenAndServerType", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentGCMVersion", + "coverage": 0.6 + }, + { + "name": "FIRInstanceIDCurrentLocale", + "coverage": 0.4583333333333333 + } + ] + }, + { + "name": "FDLURLComponents.m", + "coverage": 0.8563922942206655, + "type": "objc", + "functions": [ + { + "name": "FDLSafelyAddKeyValuePairToDictionary", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkGoogleAnalyticsParameters parameters]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkGoogleAnalyticsParameters parametersWithSource:medium:campaign:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters init]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters initWithSource:medium:campaign:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters setSource:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters source]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters setMedium:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters medium]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters setCampaign:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters campaign]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters setTerm:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters term]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters setContent:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters content]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkGoogleAnalyticsParameters dictionaryRepresentation]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkIOSParameters parametersWithBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters initWithBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters bundleID]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters setAppStoreID:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters appStoreID]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters setFallbackURL:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters fallbackURL]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters setCustomScheme:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters customScheme]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters setMinimumAppVersion:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters minimumAppVersion]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters setIPadBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters iPadBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters setIPadFallbackURL:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters iPadFallbackURL]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkIOSParameters dictionaryRepresentation]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkItunesConnectAnalyticsParameters parameters]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkItunesConnectAnalyticsParameters init]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkItunesConnectAnalyticsParameters setAffiliateToken:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkItunesConnectAnalyticsParameters affiliateToken]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkItunesConnectAnalyticsParameters setCampaignToken:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkItunesConnectAnalyticsParameters campaignToken]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkItunesConnectAnalyticsParameters setProviderToken:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkItunesConnectAnalyticsParameters providerToken]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkItunesConnectAnalyticsParameters dictionaryRepresentation]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkAndroidParameters parametersWithPackageName:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkAndroidParameters initWithPackageName:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkAndroidParameters packageName]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkAndroidParameters setMinimumVersion:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkAndroidParameters minimumVersion]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkAndroidParameters setFallbackURL:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkAndroidParameters fallbackURL]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkAndroidParameters dictionaryRepresentation]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkSocialMetaTagParameters parameters]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkSocialMetaTagParameters init]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkSocialMetaTagParameters setTitle:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkSocialMetaTagParameters title]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkSocialMetaTagParameters setDescriptionText:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkSocialMetaTagParameters descriptionText]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkSocialMetaTagParameters setImageURL:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkSocialMetaTagParameters imageURL]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkSocialMetaTagParameters dictionaryRepresentation]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkNavigationInfoParameters parameters]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkNavigationInfoParameters init]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkNavigationInfoParameters isForcedRedirectEnabled]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkNavigationInfoParameters setForcedRedirectEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkNavigationInfoParameters dictionaryRepresentation]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkOtherPlatformParameters parameters]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkOtherPlatformParameters init]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkOtherPlatformParameters fallbackUrl]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkOtherPlatformParameters setFallbackUrl:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkOtherPlatformParameters dictionaryRepresentation]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkComponentsOptions options]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkComponentsOptions init]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkComponents componentsWithLink:domain:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkComponents initWithLink:domain:]", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkComponents componentsWithLink:domainURIPrefix:]", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkComponents initWithLink:domainURIPrefix:]", + "coverage": 0.8 + }, + { + "name": "+[FIRDynamicLinkComponents shortenURL:options:completion:]", + "coverage": 0.9333333333333333 + }, + { + "name": "__58+[FIRDynamicLinkComponents shortenURL:options:completion:]_block_invoke", + "coverage": 0.3088235294117647 + }, + { + "name": "__58+[FIRDynamicLinkComponents shortenURL:options:completion:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRDynamicLinkComponents shortenWithCompletion:]", + "coverage": 0.3333333333333333 + }, + { + "name": "-[FIRDynamicLinkComponents url]", + "coverage": 1 + }, + { + "name": "__31-[FIRDynamicLinkComponents url]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRDynamicLinkComponents sendHTTPRequest:completion:]", + "coverage": 0 + }, + { + "name": "__55+[FIRDynamicLinkComponents sendHTTPRequest:completion:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FIRDynamicLinkComponents shorteningRequestForLongURL:options:]", + "coverage": 0.9512195121951219 + } + ] + }, + { + "name": "FIRInstanceIDStore.m", + "coverage": 0.8579234972677595, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDStore initWithDelegate:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore initWithCheckinStore:tokenStore:delegate:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStore hasSubDirectory:]", + "coverage": 0.8181818181818182 + }, + { + "name": "+[FIRInstanceIDStore supportedDirectory]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStore pathForSupportSubDirectory:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStore createSubDirectory:]", + "coverage": 0.6 + }, + { + "name": "+[FIRInstanceIDStore removeSubDirectory:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore resetCredentialsIfNeeded]", + "coverage": 1 + }, + { + "name": "__46-[FIRInstanceIDStore resetCredentialsIfNeeded]_block_invoke", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRInstanceIDStore tokenInfoWithAuthorizedEntity:scope:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRInstanceIDStore cachedTokenInfos]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore saveTokenInfo:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore removeCachedTokenWithAuthorizedEntity:scope:]", + "coverage": 0.4444444444444444 + }, + { + "name": "-[FIRInstanceIDStore removeAllCachedTokensWithHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore saveCheckinPreferences:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore cachedCheckinPreferences]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore removeCheckinPreferencesWithHandler:]", + "coverage": 1 + }, + { + "name": "__58-[FIRInstanceIDStore removeCheckinPreferencesWithHandler:]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FIRApp.m", + "coverage": 0.8848, + "type": "objc", + "functions": [ + { + "name": "+[FIRApp configure]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithOptions:]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithName:options:]", + "coverage": 0.9333333333333333 + }, + { + "name": "+[FIRApp defaultApp]", + "coverage": 1 + }, + { + "name": "+[FIRApp appNamed:]", + "coverage": 1 + }, + { + "name": "+[FIRApp allApps]", + "coverage": 1 + }, + { + "name": "+[FIRApp resetApps]", + "coverage": 1 + }, + { + "name": "-[FIRApp deleteApp:]", + "coverage": 0.7916666666666666 + }, + { + "name": "+[FIRApp addAppToAppDictionary:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRApp initInstanceWithName:options:]", + "coverage": 1 + }, + { + "name": "-[FIRApp configureCore]", + "coverage": 0.7962962962962963 + }, + { + "name": "-[FIRApp options]", + "coverage": 1 + }, + { + "name": "-[FIRApp setDataCollectionDefaultEnabled:]", + "coverage": 0.9354838709677419 + }, + { + "name": "-[FIRApp isDataCollectionDefaultEnabled]", + "coverage": 1 + }, + { + "name": "+[FIRApp sendNotificationsToSDKs:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForMissingOptions]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForSubspecConfigurationFailureWithDomain:errorCode:service:reason:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForInvalidAppID]", + "coverage": 0 + }, + { + "name": "+[FIRApp isDefaultAppConfigured]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerLibrary:withVersion:]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerInternalLibrary:withName:withVersion:]", + "coverage": 1 + }, + { + "name": "__55+[FIRApp registerInternalLibrary:withName:withVersion:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRApp firebaseUserAgent]", + "coverage": 1 + }, + { + "name": "-[FIRApp checkExpectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp isAppIDValid]", + "coverage": 1 + }, + { + "name": "+[FIRApp validateAppID:]", + "coverage": 0.9473684210526315 + }, + { + "name": "+[FIRApp actualBundleID]", + "coverage": 0 + }, + { + "name": "+[FIRApp validateAppIDFormat:withVersion:]", + "coverage": 0.8450704225352113 + }, + { + "name": "+[FIRApp validateAppIDFingerprint:withVersion:]", + "coverage": 0.8620689655172413 + }, + { + "name": "-[FIRApp expectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp clearDataCollectionSwitchFromUserDefaults]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromUserDefaultsForApp:]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromPlist]", + "coverage": 1 + }, + { + "name": "__43+[FIRApp readDataCollectionSwitchFromPlist]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRApp sendLogsWithServiceName:version:error:]", + "coverage": 0.2631578947368421 + } + ] + }, + { + "name": "FIRInstanceIDTokenInfo.m", + "coverage": 0.889763779527559, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenInfo initWithAuthorizedEntity:scope:token:appVersion:firebaseAppID:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenInfo isFresh]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenInfo initWithCoder:]", + "coverage": 0.78125 + }, + { + "name": "-[FIRInstanceIDTokenInfo encodeWithCoder:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinPreferences.m", + "coverage": 0.8913043478260869, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDCheckinPreferences checkinPlistContents]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences hasCheckinInfo]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences hasValidCheckinInfo]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences setHasPreCachedAuthCredentials:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinService.m", + "coverage": 0.896414342629482, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDCheckinService init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinService dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinService checkinWithExistingCheckin:completion:]", + "coverage": 0.9649122807017544 + }, + { + "name": "__69-[FIRInstanceIDCheckinService checkinWithExistingCheckin:completion:]_block_invoke", + "coverage": 0.7105263157894737 + }, + { + "name": "-[FIRInstanceIDCheckinService stopFetching]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinService checkinParametersWithExistingCheckin:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDCheckinService setCheckinTestBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDAPNSInfo.m", + "coverage": 0.9090909090909091, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDAPNSInfo initWithDeviceToken:isSandbox:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAPNSInfo initWithTokenOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAPNSInfo initWithCoder:]", + "coverage": 0.75 + }, + { + "name": "-[FIRInstanceIDAPNSInfo encodeWithCoder:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAPNSInfo isEqualToAPNSInfo:]", + "coverage": 0.7142857142857143 + } + ] + }, + { + "name": "FIRInstanceIDAuthService.m", + "coverage": 0.9166666666666666, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDAuthService initWithCheckinService:store:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService initWithStore:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService scheduleCheckin:]", + "coverage": 0.7 + }, + { + "name": "-[FIRInstanceIDAuthService startCheckinTimerWithDuration:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService clearScheduledCheckinTimer]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService onScheduledCheckinTimerFired:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService performScheduledCheckin]", + "coverage": 1 + }, + { + "name": "__51-[FIRInstanceIDAuthService performScheduledCheckin]_block_invoke", + "coverage": 1 + }, + { + "name": "__51-[FIRInstanceIDAuthService performScheduledCheckin]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService calculateNextCheckinRetryIntervalInSeconds]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FIRInstanceIDAuthService hasValidCheckinInfo]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]", + "coverage": 1 + }, + { + "name": "__56-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__56-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__56-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService checkinPreferences]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService stopCheckinRequest]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService resetCheckinWithHandler:]", + "coverage": 0 + }, + { + "name": "__52-[FIRInstanceIDAuthService resetCheckinWithHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDAuthService notifyCheckinHandlersWithCheckin:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService cachedCheckinMatchesCheckin:]", + "coverage": 0.5714285714285714 + } + ] + }, + { + "name": "FIRInstanceIDKeychain.m", + "coverage": 0.9227053140096618, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDKeychain sharedInstance]", + "coverage": 1 + }, + { + "name": "__39+[FIRInstanceIDKeychain sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain itemWithQuery:]", + "coverage": 1 + }, + { + "name": "__39-[FIRInstanceIDKeychain itemWithQuery:]_block_invoke", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRInstanceIDKeychain removeItemWithQuery:handler:]", + "coverage": 1 + }, + { + "name": "__53-[FIRInstanceIDKeychain removeItemWithQuery:handler:]_block_invoke", + "coverage": 0.8260869565217391 + }, + { + "name": "__53-[FIRInstanceIDKeychain removeItemWithQuery:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain addItemWithQuery:handler:]", + "coverage": 1 + }, + { + "name": "__50-[FIRInstanceIDKeychain addItemWithQuery:handler:]_block_invoke", + "coverage": 0.6470588235294118 + }, + { + "name": "__50-[FIRInstanceIDKeychain addItemWithQuery:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain generateKeyPairWithPrivateTag:publicTag:]", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceIDKeychain generateKeyPairWithPrivateTag:publicTag:]_block_invoke", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "FIRInstanceIDAuthKeyChain.m", + "coverage": 0.9405405405405406, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDAuthKeychain initWithIdentifier:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDAuthKeychain keychainQueryForService:account:generic:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain keychainQueryForService:account:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain itemsMatchingService:account:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain dataForService:account:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain removeItemsMatchingService:account:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain setData:forService:accessibility:account:handler:]", + "coverage": 0.9183673469387755 + }, + { + "name": "__78-[FIRInstanceIDAuthKeychain setData:forService:accessibility:account:handler:]_block_invoke", + "coverage": 0.72 + } + ] + }, + { + "name": "FIROptions.m", + "coverage": 0.9489795918367347, + "type": "objc", + "functions": [ + { + "name": "+[FIROptions defaultOptions]", + "coverage": 0.8461538461538461 + }, + { + "name": "+[FIROptions initialize]", + "coverage": 1 + }, + { + "name": "+[FIROptions defaultOptionsDictionary]", + "coverage": 1 + }, + { + "name": "+[FIROptions plistFilePathWithName:]", + "coverage": 1 + }, + { + "name": "+[FIROptions resetDefaultOptions]", + "coverage": 1 + }, + { + "name": "-[FIROptions initInternalWithOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithContentsOfFile:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithGoogleAppID:GCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions APIKey]", + "coverage": 1 + }, + { + "name": "-[FIROptions checkEditingLocked]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAPIKey:]", + "coverage": 1 + }, + { + "name": "-[FIROptions clientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions trackingID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setTrackingID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions GCMSenderID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions projectID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setProjectID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions androidClientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAndroidClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions googleAppID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGoogleAppID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions libraryVersionID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setLibraryVersionID:]", + "coverage": 0 + }, + { + "name": "-[FIROptions databaseURL]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDatabaseURL:]", + "coverage": 1 + }, + { + "name": "-[FIROptions storageBucket]", + "coverage": 1 + }, + { + "name": "-[FIROptions setStorageBucket:]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDeepLinkURLScheme:]", + "coverage": 1 + }, + { + "name": "-[FIROptions bundleID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionaryWithInfoDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIROptions isMeasurementEnabled]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIROptions isAnalyticsCollectionExpicitlySet]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionEnabled]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionDeactivated]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsEnabled]", + "coverage": 0 + }, + { + "name": "-[FIROptions isSignInEnabled]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAnalyticsConfiguration.m", + "coverage": 0.9545454545454546, + "type": "objc", + "functions": [ + { + "name": "+[FIRAnalyticsConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__43+[FIRAnalyticsConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration postNotificationName:value:]", + "coverage": 0.75 + }, + { + "name": "-[FIRAnalyticsConfiguration setMinimumSessionInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setSessionTimeoutInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:persistSetting:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinPreferences+Internal.m", + "coverage": 0.9692307692307692, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) preferencesFromKeychainContents:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) initWithDeviceID:secretToken:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) reset]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) updateWithCheckinPlistContents:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) checkinKeychainContent]", + "coverage": 0.75 + }, + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) checkinDeviceIDFromKeychainContent:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) checkinSecretFromKeychainContent:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) checkinKeychainContent:forIndex:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentContainer.m", + "coverage": 0.9818181818181818, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:]", + "coverage": 1 + }, + { + "name": "__55+[FIRComponentContainer registerAsComponentRegistrant:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:inSet:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:registrants:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer populateComponentsFromRegisteredClasses:forApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer instantiateInstanceForProtocol:withBlock:]", + "coverage": 0.9285714285714286 + }, + { + "name": "-[FIRComponentContainer instanceForProtocol:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer removeAllCachedInstances]", + "coverage": 1 + } + ] + }, + { + "name": "main.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "main", + "coverage": 1 + } + ] + }, + { + "name": "FIRDLScionLogging.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "FIRDLLogEventToScion", + "coverage": 1 + } + ] + }, + { + "name": "FIRDLRetrievalProcessFactory.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRDLRetrievalProcessFactory initWithNetworkingService:clientID:URLScheme:APIKey:FDLSDKVersion:delegate:]", + "coverage": 1 + }, + { + "name": "-[FIRDLRetrievalProcessFactory automaticRetrievalProcess]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDKeyPair.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDKeyPair initWithPrivateKey:publicKey:publicKeyData:privateKeyData:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair isValid]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair publicKey]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair privateKey]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDKeyPairUtilities.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDWebSafeBase64", + "coverage": 1 + }, + { + "name": "FIRInstanceIDSHA1", + "coverage": 1 + }, + { + "name": "FIRInstanceIDKeyPairQuery", + "coverage": 1 + }, + { + "name": "FIRInstanceIDAppIdentity", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentType.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentType instanceForProtocol:inContainer:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponent.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponent componentWithProtocol:creationBlock:]", + "coverage": 1 + }, + { + "name": "+[FIRComponent componentWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + }, + { + "name": "-[FIRComponent initWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAppAssociationRegistration.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRAppAssociationRegistration registeredObjectWithHost:key:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDTokenStore.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDTokenStore defaultStore]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore initWithKeychain:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenStore serviceKeyForAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore tokenInfoWithAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore cachedTokenInfos]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenStore tokenInfoFromKeychainItem:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore saveTokenInfo:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore removeTokenWithAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore removeAllTokensWithHandler:]", + "coverage": 1 + } + ] + } + ] + }, + { + "name": "GTMSessionFetcher.framework", + "coverage": 0.26761433868974044, + "files": [ + { + "name": "GTMSessionFetcherLogging.m", + "coverage": 0.006242197253433208, + "type": "objc", + "functions": [ + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) setLoggingDirectory:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) loggingDirectory]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) setLogDirectoryForCurrentRun:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) logDirectoryForCurrentRun]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) setLoggingEnabled:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) isLoggingEnabled]", + "coverage": 1 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) setLoggingToFileEnabled:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) isLoggingToFileEnabled]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) setLoggingProcessName:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) loggingProcessName]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) setLoggingDateStamp:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) loggingDateStamp]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) processNameLogPrefix]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) symlinkNameSuffix]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) htmlFileName]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLogging) deleteLogDirectoriesOlderThanDate:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher(GTMSessionFetcherLogging) formattedStringFromData:contentType:JSON:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher(GTMSessionFetcherLogging) stringFromStreamData:contentType:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher(GTMSessionFetcherLogging) logFetchWithError:]", + "coverage": 0.004866180048661801 + }, + { + "name": "-[GTMSessionFetcher(GTMSessionFetcherLogging) loggedInputStreamForInputStream:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher(GTMSessionFetcherLogging) loggedStreamProviderForStreamProvider:]", + "coverage": 0 + }, + { + "name": "__85-[GTMSessionFetcher(GTMSessionFetcherLogging) loggedStreamProviderForStreamProvider:]_block_invoke", + "coverage": 0 + }, + { + "name": "__85-[GTMSessionFetcher(GTMSessionFetcherLogging) loggedStreamProviderForStreamProvider:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher(GTMSessionFetcherLoggingUtilities) inputStream:readIntoBuffer:length:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLoggingUtilities) snipSubstringOfString:betweenStartString:endString:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher(GTMSessionFetcherLoggingUtilities) headersStringForDictionary:]", + "coverage": 0 + } + ] + }, + { + "name": "GTMSessionUploadFetcher.m", + "coverage": 0.01529535864978903, + "type": "objc", + "functions": [ + { + "name": "+[GTMSessionUploadFetcher load]", + "coverage": 1 + }, + { + "name": "+[GTMSessionUploadFetcher uploadFetcherWithRequest:uploadMIMEType:chunkSize:fetcherService:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionUploadFetcher uploadFetcherWithLocation:uploadMIMEType:chunkSize:fetcherService:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionUploadFetcher uploadFetcherForSessionIdentifierMetadata:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionUploadFetcher uploadFetcherWithRequest:fetcherService:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionUploadFetcher uploadFetcherPointerArrayForBackgroundSessions]", + "coverage": 1 + }, + { + "name": "__73+[GTMSessionUploadFetcher uploadFetcherPointerArrayForBackgroundSessions]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GTMSessionUploadFetcher uploadFetcherForSessionIdentifier:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionUploadFetcher uploadFetchersForBackgroundSessions]", + "coverage": 0.2857142857142857 + }, + { + "name": "-[GTMSessionUploadFetcher setUploadData:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadData]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setUploadFileHandle:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadFileHandle]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setUploadFileURL:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadFileURL]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setUploadFileLength:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setUploadDataLength:provider:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadDataProvider]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setUploadMIMEType:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadMIMEType]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setChunkSize:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher chunkSize]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setupRequestHeaders]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setLocationURL:uploadMIMEType:chunkSize:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher fullUploadLength]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher generateChunkSubdataWithOffset:length:response:]", + "coverage": 0 + }, + { + "name": "__74-[GTMSessionUploadFetcher generateChunkSubdataWithOffset:length:response:]_block_invoke", + "coverage": 0 + }, + { + "name": "__74-[GTMSessionUploadFetcher generateChunkSubdataWithOffset:length:response:]_block_invoke.237", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher generateChunkSubdataFromFileHandle:offset:length:response:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher generateChunkSubdataFromFileURL:offset:length:response:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadChunkUnavailableErrorWithDescription:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher prematureFailureErrorWithUserInfo:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionUploadFetcher uploadStatusFromResponseHeaders:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setCompletionHandler:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setDelegateCallbackQueue:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher delegateCallbackQueue]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher isRestartedUpload]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher chunkFetcher]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setChunkFetcher:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setFetcherInFlight:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher fetcherInFlight]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setCancellationHandler:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher cancellationHandler]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher beginFetchForRetry]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher beginFetchWithCompletionHandler:]", + "coverage": 0 + }, + { + "name": "__59-[GTMSessionUploadFetcher beginFetchWithCompletionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__59-[GTMSessionUploadFetcher beginFetchWithCompletionHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher beginChunkFetches]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher shouldReleaseCallbacksUponCompletion]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher invokeFinalCallbackWithData:error:shouldInvalidateLocation:]", + "coverage": 0 + }, + { + "name": "__86-[GTMSessionUploadFetcher invokeFinalCallbackWithData:error:shouldInvalidateLocation:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher releaseUploadAndBaseCallbacks:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher stopFetchReleasingCallbacks:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadNextChunkWithOffset:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher sendQueryForUploadOffsetWithFetcherProperties:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher queryFetcher:finishedWithData:error:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher sendCancelUploadWithFetcherProperties:]", + "coverage": 0 + }, + { + "name": "__65-[GTMSessionUploadFetcher sendCancelUploadWithFetcherProperties:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadNextChunkWithOffset:fetcherProperties:]", + "coverage": 0 + }, + { + "name": "__71-[GTMSessionUploadFetcher uploadNextChunkWithOffset:fetcherProperties:]_block_invoke", + "coverage": 0 + }, + { + "name": "__71-[GTMSessionUploadFetcher uploadNextChunkWithOffset:fetcherProperties:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher beginChunkFetcher:offset:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher attachSendProgressBlockToChunkFetcher:]", + "coverage": 0 + }, + { + "name": "__65-[GTMSessionUploadFetcher attachSendProgressBlockToChunkFetcher:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadSessionIdentifierMetadata]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadFetcherWithProperties:isQueryFetch:]", + "coverage": 0 + }, + { + "name": "__68-[GTMSessionUploadFetcher uploadFetcherWithProperties:isQueryFetch:]_block_invoke", + "coverage": 0 + }, + { + "name": "__68-[GTMSessionUploadFetcher uploadFetcherWithProperties:isQueryFetch:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher chunkFetcher:finishedWithData:error:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher destroyChunkFetcher]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher invokeDelegateWithDidSendBytes:totalBytesSent:totalBytesExpectedToSend:]", + "coverage": 0 + }, + { + "name": "__98-[GTMSessionUploadFetcher invokeDelegateWithDidSendBytes:totalBytesSent:totalBytesExpectedToSend:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher retrieveUploadChunkGranularityFromResponseHeaders:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher isPaused]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher pauseFetching]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher resumeFetching]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher stopFetching]", + "coverage": 0 + }, + { + "name": "__39-[GTMSessionUploadFetcher stopFetching]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher triggerCancellationHandlerForFetch:data:error:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher updateChunkFetcher:forChunkAtOffset:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionUploadFetcher removePointer:fromPointerArray:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher useBackgroundSession]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setUseBackgroundSession:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher canFetchWithBackgroundSession]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher responseHeaders]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher statusCodeUnsynchronized]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setStatusCode:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher initialBodyLength]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setInitialBodyLength:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher initialBodySent]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setInitialBodySent:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher uploadLocationURL]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher setUploadLocationURL:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher activeFetcher]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher isFetching]", + "coverage": 0 + }, + { + "name": "-[GTMSessionUploadFetcher waitForCompletionWithTimeout:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher(GTMSessionUploadFetcherMethods) parentUploadFetcher]", + "coverage": 0 + } + ] + }, + { + "name": "GTMSessionFetcher.m", + "coverage": 0.3696453247351451, + "type": "objc", + "functions": [ + { + "name": "InitialMinRetryInterval", + "coverage": 1 + }, + { + "name": "IsLocalhost", + "coverage": 0 + }, + { + "name": "GTMErrorUserInfoForData", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher load]", + "coverage": 1 + }, + { + "name": "+[GTMSessionFetcher fetcherWithRequest:]", + "coverage": 1 + }, + { + "name": "+[GTMSessionFetcher fetcherWithURL:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher fetcherWithURLString:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher fetcherWithDownloadResumeData:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher fetcherWithSessionIdentifier:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher sessionIdentifierToFetcherMap]", + "coverage": 1 + }, + { + "name": "__50+[GTMSessionFetcher sessionIdentifierToFetcherMap]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GTMSessionFetcher appAllowsInsecureRequests]", + "coverage": 1 + }, + { + "name": "__46+[GTMSessionFetcher appAllowsInsecureRequests]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher init]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher initWithRequest:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher initWithRequest:configuration:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher description]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher dealloc]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher beginFetchWithCompletionHandler:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher beginFetchForRetry]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher completionHandlerWithTarget:didFinishSelector:]", + "coverage": 0 + }, + { + "name": "__67-[GTMSessionFetcher completionHandlerWithTarget:didFinishSelector:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher beginFetchWithDelegate:didFinishSelector:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher beginFetchMayDelay:mayAuthorize:]", + "coverage": 0.5222222222222223 + }, + { + "name": "__53-[GTMSessionFetcher beginFetchMayDelay:mayAuthorize:]_block_invoke", + "coverage": 0 + }, + { + "name": "__53-[GTMSessionFetcher beginFetchMayDelay:mayAuthorize:]_block_invoke.276", + "coverage": 0 + }, + { + "name": "__53-[GTMSessionFetcher beginFetchMayDelay:mayAuthorize:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__53-[GTMSessionFetcher beginFetchMayDelay:mayAuthorize:]_block_invoke.377", + "coverage": 0 + }, + { + "name": "GTMDataFromInputStream", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher simulateFetchForTestBlock]", + "coverage": 1 + }, + { + "name": "__46-[GTMSessionFetcher simulateFetchForTestBlock]_block_invoke", + "coverage": 0.44642857142857145 + }, + { + "name": "__46-[GTMSessionFetcher simulateFetchForTestBlock]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__46-[GTMSessionFetcher simulateFetchForTestBlock]_block_invoke_3", + "coverage": 0 + }, + { + "name": "__46-[GTMSessionFetcher simulateFetchForTestBlock]_block_invoke_4", + "coverage": 0 + }, + { + "name": "__46-[GTMSessionFetcher simulateFetchForTestBlock]_block_invoke.434", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher simulateByteTransferReportWithDataLength:block:]", + "coverage": 0 + }, + { + "name": "__68-[GTMSessionFetcher simulateByteTransferReportWithDataLength:block:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]", + "coverage": 0.40718562874251496 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke.457", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke_2.471", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke.478", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke_2.479", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke.486", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke.493", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke.499", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke.509", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke_2.510", + "coverage": 0 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke.517", + "coverage": 1 + }, + { + "name": "__95-[GTMSessionFetcher simulateDataCallbacksForTestBlockWithBodyData:response:responseData:error:]_block_invoke_2.522", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher simulateByteTransferWithData:block:]", + "coverage": 0 + }, + { + "name": "__56-[GTMSessionFetcher simulateByteTransferWithData:block:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setSessionTask:]", + "coverage": 0.8666666666666667 + }, + { + "name": "-[GTMSessionFetcher sessionTask]", + "coverage": 1 + }, + { + "name": "+[GTMSessionFetcher fetcherUserDefaults]", + "coverage": 1 + }, + { + "name": "__40+[GTMSessionFetcher fetcherUserDefaults]_block_invoke", + "coverage": 0.875 + }, + { + "name": "-[GTMSessionFetcher addPersistedBackgroundSessionToDefaults]", + "coverage": 0.2631578947368421 + }, + { + "name": "-[GTMSessionFetcher removePersistedBackgroundSessionFromDefaults]", + "coverage": 0.11538461538461539 + }, + { + "name": "+[GTMSessionFetcher activePersistedBackgroundSessions]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher fetchersForBackgroundSessions]", + "coverage": 0.4090909090909091 + }, + { + "name": "+[GTMSessionFetcher application:handleEventsForBackgroundURLSession:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher sessionIdentifier]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setSessionIdentifier:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setSessionIdentifierInternal:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher sessionUserInfo]", + "coverage": 0 + }, + { + "name": "__36-[GTMSessionFetcher sessionUserInfo]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setSessionUserInfo:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher sessionIdentifierDefaultMetadata]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher restoreDefaultStateForSessionIdentifierMetadata]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher sessionIdentifierMetadata]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher sessionIdentifierMetadataUnsynchronized]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher createSessionIdentifierWithMetadata:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher failToBeginFetchWithError:]", + "coverage": 0.8095238095238095 + }, + { + "name": "+[GTMSessionFetcher staticCookieStorage]", + "coverage": 1 + }, + { + "name": "__40+[GTMSessionFetcher staticCookieStorage]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher endBackgroundTask]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher authorizeRequest]", + "coverage": 0.65 + }, + { + "name": "-[GTMSessionFetcher authorizer:request:finishedWithError:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher canFetchWithBackgroundSession]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher isFetching]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher isFetchingUnsynchronized]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher response]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher responseUnsynchronized]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher statusCode]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher statusCodeUnsynchronized]", + "coverage": 0.7333333333333333 + }, + { + "name": "-[GTMSessionFetcher responseHeaders]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher responseHeadersUnsynchronized]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher releaseCallbacks]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher forgetSessionIdentifierForFetcher]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher forgetSessionIdentifierForFetcherWithoutSyncCheck]", + "coverage": 0.4444444444444444 + }, + { + "name": "-[GTMSessionFetcher stopFetching]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher stopFetchReleasingCallbacks:]", + "coverage": 0.8269230769230769 + }, + { + "name": "__49-[GTMSessionFetcher stopFetchReleasingCallbacks:]_block_invoke", + "coverage": 0 + }, + { + "name": "__49-[GTMSessionFetcher stopFetchReleasingCallbacks:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setStopNotificationNeeded:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher sendStopNotificationIfNeeded]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher retryFetch]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher waitForCompletionWithTimeout:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher setGlobalTestBlock:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher setSubstituteUIApplication:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher substituteUIApplication]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher fetcherUIApplication]", + "coverage": 1 + }, + { + "name": "__41+[GTMSessionFetcher fetcherUIApplication]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:]", + "coverage": 0 + }, + { + "name": "__93-[GTMSessionFetcher URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__93-[GTMSessionFetcher URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:dataTask:didReceiveResponse:completionHandler:]", + "coverage": 0 + }, + { + "name": "__78-[GTMSessionFetcher URLSession:dataTask:didReceiveResponse:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__78-[GTMSessionFetcher URLSession:dataTask:didReceiveResponse:completionHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__78-[GTMSessionFetcher URLSession:dataTask:didReceiveResponse:completionHandler:]_block_invoke.804", + "coverage": 0 + }, + { + "name": "__78-[GTMSessionFetcher URLSession:dataTask:didReceiveResponse:completionHandler:]_block_invoke_2.805", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:dataTask:didBecomeDownloadTask:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:task:didReceiveChallenge:completionHandler:]", + "coverage": 0 + }, + { + "name": "__75-[GTMSessionFetcher URLSession:task:didReceiveChallenge:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher respondToChallenge:completionHandler:]", + "coverage": 0 + }, + { + "name": "__58-[GTMSessionFetcher respondToChallenge:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher redirectURLWithOriginalRequestURL:redirectRequestURL:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher evaluateServerTrust:forRequest:completionHandler:]", + "coverage": 0 + }, + { + "name": "__70+[GTMSessionFetcher evaluateServerTrust:forRequest:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher invokeOnCallbackQueueUnlessStopped:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher invokeOnCallbackQueueAfterUserStopped:block:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher invokeOnCallbackUnsynchronizedQueueAfterUserStopped:block:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher invokeOnCallbackQueue:afterUserStopped:block:]", + "coverage": 1 + }, + { + "name": "__66-[GTMSessionFetcher invokeOnCallbackQueue:afterUserStopped:block:]_block_invoke", + "coverage": 0.8076923076923077 + }, + { + "name": "-[GTMSessionFetcher invokeFetchCallbacksOnCallbackQueueWithData:error:]", + "coverage": 1 + }, + { + "name": "__71-[GTMSessionFetcher invokeFetchCallbacksOnCallbackQueueWithData:error:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher postNotificationOnMainThreadWithName:userInfo:requireAsync:]", + "coverage": 1 + }, + { + "name": "__80-[GTMSessionFetcher postNotificationOnMainThreadWithName:userInfo:requireAsync:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher URLSession:task:needNewBodyStream:]", + "coverage": 0 + }, + { + "name": "__55-[GTMSessionFetcher URLSession:task:needNewBodyStream:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:]", + "coverage": 0 + }, + { + "name": "__93-[GTMSessionFetcher URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:dataTask:didReceiveData:]", + "coverage": 0 + }, + { + "name": "__56-[GTMSessionFetcher URLSession:dataTask:didReceiveData:]_block_invoke", + "coverage": 0 + }, + { + "name": "__56-[GTMSessionFetcher URLSession:dataTask:didReceiveData:]_block_invoke.895", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:dataTask:willCacheResponse:completionHandler:]", + "coverage": 0 + }, + { + "name": "__77-[GTMSessionFetcher URLSession:dataTask:willCacheResponse:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:]", + "coverage": 0 + }, + { + "name": "__102-[GTMSessionFetcher URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:downloadTask:didFinishDownloadingToURL:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:task:didCompleteWithError:]", + "coverage": 0.23880597014925373 + }, + { + "name": "__58-[GTMSessionFetcher URLSession:task:didCompleteWithError:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSessionDidFinishEventsForBackgroundURLSession:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher URLSession:didBecomeInvalidWithError:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher finishWithError:shouldRetry:]", + "coverage": 0.6165413533834586 + }, + { + "name": "__49-[GTMSessionFetcher finishWithError:shouldRetry:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher shouldReleaseCallbacksUponCompletion]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher logNowWithError:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher isRetryError:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher shouldRetryNowForStatus:error:forceAssumeRetry:response:]", + "coverage": 0.4025974025974026 + }, + { + "name": "__77-[GTMSessionFetcher shouldRetryNowForStatus:error:forceAssumeRetry:response:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher hasRetryAfterInterval]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher retryAfterInterval]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher beginRetryTimer]", + "coverage": 0 + }, + { + "name": "__36-[GTMSessionFetcher beginRetryTimer]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher retryTimerFired:]", + "coverage": 0 + }, + { + "name": "__37-[GTMSessionFetcher retryTimerFired:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher destroyRetryTimer]", + "coverage": 0.5789473684210527 + }, + { + "name": "-[GTMSessionFetcher retryCount]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher nextRetryInterval]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher nextRetryIntervalUnsynchronized]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher retryTimer]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher isRetryEnabled]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher isRetryEnabledUnsynchronized]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setRetryEnabled:]", + "coverage": 0.45 + }, + { + "name": "-[GTMSessionFetcher maxRetryInterval]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setMaxRetryInterval:]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[GTMSessionFetcher minRetryInterval]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setMinRetryInterval:]", + "coverage": 0.9333333333333333 + }, + { + "name": "-[GTMSessionFetcher systemCompletionHandler]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setSystemCompletionHandler:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher setSystemCompletionHandler:forSessionIdentifier:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcher systemCompletionHandlerForSessionIdentifier:]", + "coverage": 0.5 + }, + { + "name": "-[GTMSessionFetcher request]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setRequest:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher mutableRequestForTesting]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher updateMutableRequest:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setRequestValue:forHTTPHeaderField:]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[GTMSessionFetcher updateRequestValue:forHTTPHeaderField:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setResponse:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher bodyLength]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher useUploadTask]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setUseUploadTask:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher bodyFileURL]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setBodyFileURL:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher bodyStreamProvider]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setBodyStreamProvider:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher authorizer]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setAuthorizer:]", + "coverage": 0.9230769230769231 + }, + { + "name": "-[GTMSessionFetcher downloadedData]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setDownloadedData:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher downloadedLength]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setDownloadedLength:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher callbackQueue]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setCallbackQueue:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher session]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher servicePriority]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setServicePriority:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setSession:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher canShareSession]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setCanShareSession:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher useBackgroundSession]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setUseBackgroundSession:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher isUsingBackgroundSession]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setUsingBackgroundSession:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher sessionNeedingInvalidation]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setSessionNeedingInvalidation:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher sessionDelegateQueue]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setSessionDelegateQueue:]", + "coverage": 0.9230769230769231 + }, + { + "name": "-[GTMSessionFetcher userStoppedFetching]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher userData]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setUserData:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher destinationFileURL]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher setDestinationFileURL:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setProperties:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcher properties]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setProperty:forKey:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher propertyForKey:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher addPropertiesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setCommentWithFormat:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher loggedStreamData]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher appendLoggedStreamData:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher clearLoggedStreamData]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher setDeferResponseBodyLogging:]", + "coverage": 0 + }, + { + "name": "__49-[GTMSessionFetcher setDeferResponseBodyLogging:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher deferResponseBodyLogging]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcher(BackwardsCompatibilityOnly) setCookieStorageMethod:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage init]", + "coverage": 1 + }, + { + "name": "-[GTMSessionCookieStorage cookies]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage setCookie:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage internalSetCookie:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage setCookies:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage setCookies:forURL:mainDocumentURL:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage deleteCookie:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage cookiesForURL:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage storeCookies:forTask:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage getCookiesForTask:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage cookieMatchingCookie:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage removeExpiredCookies]", + "coverage": 0 + }, + { + "name": "+[GTMSessionCookieStorage hasCookieExpired:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage removeAllCookies]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage cookieAcceptPolicy]", + "coverage": 0 + }, + { + "name": "-[GTMSessionCookieStorage setCookieAcceptPolicy:]", + "coverage": 0 + }, + { + "name": "GTMSessionFetcherAssertValidSelector", + "coverage": 0 + }, + { + "name": "GTMFetcherCleanedUserAgentString", + "coverage": 0.9230769230769231 + }, + { + "name": "GTMFetcherSystemVersionString", + "coverage": 1 + }, + { + "name": "__GTMFetcherSystemVersionString_block_invoke", + "coverage": 1 + }, + { + "name": "GTMFetcherStandardUserAgentString", + "coverage": 1 + }, + { + "name": "GTMFetcherApplicationIdentifier", + "coverage": 1 + }, + { + "name": "-[GTMSessionSyncMonitorInternal initWithSynchronizationObject:allowRecursive:functionName:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionSyncMonitorInternal dealloc]", + "coverage": 1 + }, + { + "name": "+[GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:]", + "coverage": 1 + } + ] + }, + { + "name": "GTMSessionFetcherService.m", + "coverage": 0.5004757373929591, + "type": "objc", + "functions": [ + { + "name": "-[GTMSessionFetcherService init]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService dealloc]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService fetcherWithRequest:fetcherClass:]", + "coverage": 0.9473684210526315 + }, + { + "name": "-[GTMSessionFetcherService fetcherWithRequest:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService fetcherWithURL:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService fetcherWithURLString:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService session]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService sessionForFetcherCreation]", + "coverage": 0.9142857142857143 + }, + { + "name": "-[GTMSessionFetcherService sessionDelegate]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService addRunningFetcher:forHost:]", + "coverage": 0.8 + }, + { + "name": "-[GTMSessionFetcherService addDelayedFetcher:forHost:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService isDelayingFetcher:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService fetcherShouldBeginFetching:]", + "coverage": 0.75 + }, + { + "name": "-[GTMSessionFetcherService startFetcher:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService delegateDispatcherForFetcher:]", + "coverage": 0.8 + }, + { + "name": "-[GTMSessionFetcherService fetcherDidCreateSession:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService fetcherDidBeginFetching:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService stopFetcher:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService fetcherDidStop:]", + "coverage": 0.6621621621621622 + }, + { + "name": "-[GTMSessionFetcherService numberOfFetchers]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService numberOfRunningFetchers]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService numberOfDelayedFetchers]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService issuedFetchers]", + "coverage": 0 + }, + { + "name": "__42-[GTMSessionFetcherService issuedFetchers]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService issuedFetchersWithRequestURL:]", + "coverage": 0 + }, + { + "name": "__57-[GTMSessionFetcherService issuedFetchersWithRequestURL:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService stopAllFetchers]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService stoppedAllFetchersDate]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService reuseSession]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService setReuseSession:]", + "coverage": 0.8235294117647058 + }, + { + "name": "-[GTMSessionFetcherService resetSession]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService resetSessionInternal]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService resetSessionForDispatcherDiscardTimer:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService unusedSessionTimeout]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService setUnusedSessionTimeout:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService abandonDispatcher]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService runningFetchersByHost]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService setRunningFetchersByHost:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService delayedFetchersByHost]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService setDelayedFetchersByHost:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService authorizer]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService setAuthorizer:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService detachAuthorizer]", + "coverage": 0.8888888888888888 + }, + { + "name": "-[GTMSessionFetcherService callbackQueue]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService setCallbackQueue:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService sessionDelegateQueue]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService setSessionDelegateQueue:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService delegateQueue]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcherService numberOfNonBackgroundSessionFetchers:]", + "coverage": 0.5555555555555556 + }, + { + "name": "+[GTMSessionFetcherService(TestingSupport) mockFetcherServiceWithFakedData:fakedError:]", + "coverage": 0 + }, + { + "name": "+[GTMSessionFetcherService(TestingSupport) mockFetcherServiceWithFakedData:fakedResponse:fakedError:]", + "coverage": 0 + }, + { + "name": "__101+[GTMSessionFetcherService(TestingSupport) mockFetcherServiceWithFakedData:fakedResponse:fakedError:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService(TestingSupport) waitForCompletionOfAllFetchersWithTimeout:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherService(BackwardsCompatibilityOnly) cookieStorageMethod]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherService(BackwardsCompatibilityOnly) setCookieStorageMethod:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher init]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher initWithParentService:sessionDiscardInterval:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher description]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher discardTimer]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher startDiscardTimer]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher destroyDiscardTimer]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher discardTimerFired:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher abandon]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher startSessionUsage]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher destroySessionAndTimer]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher setFetcher:forTask:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher removeFetcher:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher fetcherForTask:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher removeTaskFromMap:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher setSession:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher session]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher discardInterval]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher setDiscardInterval:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:didBecomeInvalidWithError:]", + "coverage": 1 + }, + { + "name": "__83-[GTMSessionFetcherSessionDelegateDispatcher URLSession:didBecomeInvalidWithError:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:task:didReceiveChallenge:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:task:needNewBodyStream:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:task:didCompleteWithError:]", + "coverage": 1 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:dataTask:didReceiveResponse:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:dataTask:didBecomeDownloadTask:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:dataTask:didReceiveData:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:dataTask:willCacheResponse:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:downloadTask:didFinishDownloadingToURL:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:]", + "coverage": 0 + }, + { + "name": "-[GTMSessionFetcherSessionDelegateDispatcher URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:]", + "coverage": 0 + } + ] + } + ] + }, + { + "name": "GoogleUtilities.framework", + "coverage": 0.3882390135946886, + "files": [ + { + "name": "GULRuntimeClassDiff.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "IsEqual", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassDiff hash]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassDiff isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassDiff description]", + "coverage": 0 + } + ] + }, + { + "name": "GULRuntimeStateHelper.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[GULRuntimeStateHelper snapshotCache]", + "coverage": 0 + }, + { + "name": "__38+[GULRuntimeStateHelper snapshotCache]_block_invoke", + "coverage": 0 + }, + { + "name": "+[GULRuntimeStateHelper captureRuntimeState]", + "coverage": 0 + }, + { + "name": "+[GULRuntimeStateHelper captureRuntimeStateOfClasses:]", + "coverage": 0 + }, + { + "name": "+[GULRuntimeStateHelper diffBetween:secondSnapshot:]", + "coverage": 0 + } + ] + }, + { + "name": "GULObjectSwizzler.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[GULObjectSwizzler setAssociatedObject:key:value:association:]", + "coverage": 0 + }, + { + "name": "+[GULObjectSwizzler getAssociatedObject:key:]", + "coverage": 0 + }, + { + "name": "-[GULObjectSwizzler initWithObject:]", + "coverage": 0 + }, + { + "name": "-[GULObjectSwizzler copySelector:fromClass:isClassSelector:]", + "coverage": 0 + }, + { + "name": "-[GULObjectSwizzler setAssociatedObjectWithKey:value:association:]", + "coverage": 0 + }, + { + "name": "-[GULObjectSwizzler getAssociatedObjectForKey:]", + "coverage": 0 + }, + { + "name": "-[GULObjectSwizzler swizzle]", + "coverage": 0 + }, + { + "name": "-[GULObjectSwizzler dealloc]", + "coverage": 0 + }, + { + "name": "-[GULObjectSwizzler isSwizzlingProxyObject]", + "coverage": 0 + } + ] + }, + { + "name": "GULSwizzledObject.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[GULSwizzledObject copyDonorSelectorsUsingObjectSwizzler:]", + "coverage": 0 + }, + { + "name": "-[GULSwizzledObject init]", + "coverage": 0 + }, + { + "name": "-[GULSwizzledObject gul_objectSwizzler]", + "coverage": 0 + }, + { + "name": "-[GULSwizzledObject gul_class]", + "coverage": 0 + }, + { + "name": "-[GULSwizzledObject respondsToSelector:]", + "coverage": 0 + } + ] + }, + { + "name": "GULRuntimeSnapshot.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[GULRuntimeSnapshot init]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot initWithClasses:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot hash]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot description]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot capture]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot diff:]", + "coverage": 0 + }, + { + "name": "__27-[GULRuntimeSnapshot diff:]_block_invoke", + "coverage": 0 + }, + { + "name": "__27-[GULRuntimeSnapshot diff:]_block_invoke.34", + "coverage": 0 + }, + { + "name": "__27-[GULRuntimeSnapshot diff:]_block_invoke.43", + "coverage": 0 + } + ] + }, + { + "name": "GULRuntimeDiff.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "IsEqual", + "coverage": 0 + }, + { + "name": "-[GULRuntimeDiff hash]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeDiff isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeDiff description]", + "coverage": 0 + } + ] + }, + { + "name": "GULRuntimeClassSnapshot.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[GULRuntimeClassSnapshot init]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot initWithClass:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot capture]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot diff:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot hash]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot description]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot captureProperties]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot captureSelectorsAndImps]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot computeDiffOfProperties:withClassDiff:]", + "coverage": 0 + }, + { + "name": "__65-[GULRuntimeClassSnapshot computeDiffOfProperties:withClassDiff:]_block_invoke", + "coverage": 0 + }, + { + "name": "__65-[GULRuntimeClassSnapshot computeDiffOfProperties:withClassDiff:]_block_invoke.46", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot computeDiffOfSelectorsAndImps:withClassDiff:]", + "coverage": 0 + }, + { + "name": "__71-[GULRuntimeClassSnapshot computeDiffOfSelectorsAndImps:withClassDiff:]_block_invoke", + "coverage": 0 + }, + { + "name": "__71-[GULRuntimeClassSnapshot computeDiffOfSelectorsAndImps:withClassDiff:]_block_invoke.57", + "coverage": 0 + }, + { + "name": "__71-[GULRuntimeClassSnapshot computeDiffOfSelectorsAndImps:withClassDiff:]_block_invoke.63", + "coverage": 0 + } + ] + }, + { + "name": "GULProxy.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[GULProxy initWithDelegate:]", + "coverage": 0 + }, + { + "name": "+[GULProxy proxyWithDelegate:]", + "coverage": 0 + }, + { + "name": "-[GULProxy forwardingTargetForSelector:]", + "coverage": 0 + }, + { + "name": "-[GULProxy forwardInvocation:]", + "coverage": 0 + }, + { + "name": "-[GULProxy methodSignatureForSelector:]", + "coverage": 0 + }, + { + "name": "-[GULProxy respondsToSelector:]", + "coverage": 0 + }, + { + "name": "-[GULProxy isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULProxy hash]", + "coverage": 0 + }, + { + "name": "-[GULProxy superclass]", + "coverage": 0 + }, + { + "name": "-[GULProxy class]", + "coverage": 0 + }, + { + "name": "-[GULProxy isKindOfClass:]", + "coverage": 0 + }, + { + "name": "-[GULProxy isMemberOfClass:]", + "coverage": 0 + }, + { + "name": "-[GULProxy conformsToProtocol:]", + "coverage": 0 + }, + { + "name": "-[GULProxy isProxy]", + "coverage": 0 + }, + { + "name": "-[GULProxy description]", + "coverage": 0 + }, + { + "name": "-[GULProxy debugDescription]", + "coverage": 0 + } + ] + }, + { + "name": "GULNetwork.m", + "coverage": 0.31044776119402984, + "type": "objc", + "functions": [ + { + "name": "-[GULNetwork init]", + "coverage": 1 + }, + { + "name": "-[GULNetwork initWithReachabilityHost:]", + "coverage": 0.8666666666666667 + }, + { + "name": "-[GULNetwork dealloc]", + "coverage": 0 + }, + { + "name": "+[GULNetwork handleEventsForBackgroundURLSessionID:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[GULNetwork postURL:payload:queue:usingBackgroundSession:completionHandler:]", + "coverage": 0.7215189873417721 + }, + { + "name": "__77-[GULNetwork postURL:payload:queue:usingBackgroundSession:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__77-[GULNetwork postURL:payload:queue:usingBackgroundSession:completionHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[GULNetwork getURL:headers:queue:usingBackgroundSession:completionHandler:]", + "coverage": 0 + }, + { + "name": "__76-[GULNetwork getURL:headers:queue:usingBackgroundSession:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__76-[GULNetwork getURL:headers:queue:usingBackgroundSession:completionHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[GULNetwork hasUploadInProgress]", + "coverage": 1 + }, + { + "name": "-[GULNetwork reachability:statusChanged:]", + "coverage": 1 + }, + { + "name": "-[GULNetwork setLoggerDelegate:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[GULNetwork handleErrorWithCode:queue:withHandler:]", + "coverage": 0 + }, + { + "name": "__52-[GULNetwork handleErrorWithCode:queue:withHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULNetwork GULNetwork_logWithLevel:messageCode:message:contexts:]", + "coverage": 0.3684210526315789 + }, + { + "name": "-[GULNetwork GULNetwork_logWithLevel:messageCode:message:context:]", + "coverage": 0.45454545454545453 + }, + { + "name": "-[GULNetwork GULNetwork_logWithLevel:messageCode:message:]", + "coverage": 0 + }, + { + "name": "GULLogLevelDescriptionFromLogLevel", + "coverage": 0 + }, + { + "name": "__GULLogLevelDescriptionFromLogLevel_block_invoke", + "coverage": 0 + }, + { + "name": "GULStringWithLogMessage", + "coverage": 0 + } + ] + }, + { + "name": "GULNSData+zlib.m", + "coverage": 0.3163841807909605, + "type": "objc", + "functions": [ + { + "name": "+[NSData(GULGzip) gul_dataByInflatingGzippedData:error:]", + "coverage": 0 + }, + { + "name": "+[NSData(GULGzip) gul_dataByGzippingData:error:]", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "GULNetworkURLSession.m", + "coverage": 0.3446054750402576, + "type": "objc", + "functions": [ + { + "name": "-[GULNetworkURLSession initWithNetworkLoggerDelegate:]", + "coverage": 1 + }, + { + "name": "+[GULNetworkURLSession handleEventsForBackgroundURLSessionID:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession sessionIDFromAsyncPOSTRequest:completionHandler:]", + "coverage": 0.6216216216216216 + }, + { + "name": "-[GULNetworkURLSession sessionIDFromAsyncGETRequest:completionHandler:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession URLSession:downloadTask:didFinishDownloadingToURL:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession URLSessionDidFinishEventsForBackgroundURLSession:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession URLSession:task:didCompleteWithError:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession URLSession:task:didReceiveChallenge:completionHandler:]", + "coverage": 0.788235294117647 + }, + { + "name": "__78-[GULNetworkURLSession URLSession:task:didReceiveChallenge:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__78-[GULNetworkURLSession URLSession:task:didReceiveChallenge:completionHandler:]_block_invoke.147", + "coverage": 0.8148148148148148 + }, + { + "name": "-[GULNetworkURLSession addSystemCompletionHandler:forSession:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession callSystemCompletionHandler:]", + "coverage": 0 + }, + { + "name": "__52-[GULNetworkURLSession callSystemCompletionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession setSessionID:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession backgroundSessionConfigWithSessionID:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession maybeRemoveTempFilesAtURL:expiringTime:]", + "coverage": 0.40476190476190477 + }, + { + "name": "-[GULNetworkURLSession removeTempItemAtURL:]", + "coverage": 0 + }, + { + "name": "+[GULNetworkURLSession fetcherWithSessionIdentifier:]", + "coverage": 0 + }, + { + "name": "+[GULNetworkURLSession sessionIDToFetcherMap]", + "coverage": 1 + }, + { + "name": "__45+[GULNetworkURLSession sessionIDToFetcherMap]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULNetworkURLSession sessionIDToFetcherMapReadWriteLock]", + "coverage": 1 + }, + { + "name": "__58+[GULNetworkURLSession sessionIDToFetcherMapReadWriteLock]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULNetworkURLSession sessionIDToSystemCompletionHandlerDictionary]", + "coverage": 0 + }, + { + "name": "__68+[GULNetworkURLSession sessionIDToSystemCompletionHandlerDictionary]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession temporaryFilePathWithSessionID:]", + "coverage": 1 + }, + { + "name": "-[GULNetworkURLSession ensureTemporaryDirectoryExists]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession excludeFromBackupForURL:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:]", + "coverage": 0 + }, + { + "name": "+[GULNetworkURLSession setSessionInFetcherMap:forSessionID:]", + "coverage": 0.5 + }, + { + "name": "+[GULNetworkURLSession sessionFromFetcherMapForSessionID:]", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession callCompletionHandler:withResponse:data:error:]", + "coverage": 0 + }, + { + "name": "__70-[GULNetworkURLSession callCompletionHandler:withResponse:data:error:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULNetworkURLSession populateSessionConfig:withRequest:]", + "coverage": 1 + } + ] + }, + { + "name": "GULUserDefaults.m", + "coverage": 0.423841059602649, + "type": "objc", + "functions": [ + { + "name": "+[GULUserDefaults standardUserDefaults]", + "coverage": 1 + }, + { + "name": "__39+[GULUserDefaults standardUserDefaults]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GULUserDefaults init]", + "coverage": 1 + }, + { + "name": "-[GULUserDefaults initWithSuiteName:]", + "coverage": 1 + }, + { + "name": "-[GULUserDefaults dealloc]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults objectForKey:]", + "coverage": 0.5 + }, + { + "name": "-[GULUserDefaults setObject:forKey:]", + "coverage": 0.4482758620689655 + }, + { + "name": "-[GULUserDefaults removeObjectForKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults integerForKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults floatForKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults doubleForKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults boolForKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults stringForKey:]", + "coverage": 1 + }, + { + "name": "-[GULUserDefaults arrayForKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults dictionaryForKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults setInteger:forKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults setFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults setDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults setBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults synchronize]", + "coverage": 0.42857142857142855 + }, + { + "name": "-[GULUserDefaults clearAllData]", + "coverage": 0 + }, + { + "name": "-[GULUserDefaults scheduleSynchronize]", + "coverage": 1 + } + ] + }, + { + "name": "GULAppDelegateSwizzler.m", + "coverage": 0.4431818181818182, + "type": "objc", + "functions": [ + { + "name": "+[GULAppDelegateObserver sharedInstance]", + "coverage": 1 + }, + { + "name": "__40+[GULAppDelegateObserver sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GULAppDelegateObserver observeUIApplication]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[GULAppDelegateObserver observeValueForKeyPath:ofObject:change:context:]", + "coverage": 0 + }, + { + "name": "+[GULAppDelegateSwizzler isAppDelegateProxyEnabled]", + "coverage": 0.8260869565217391 + }, + { + "name": "+[GULAppDelegateSwizzler registerAppDelegateInterceptor:]", + "coverage": 0.47058823529411764 + }, + { + "name": "+[GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:]", + "coverage": 0 + }, + { + "name": "+[GULAppDelegateSwizzler proxyOriginalDelegate]", + "coverage": 1 + }, + { + "name": "__47+[GULAppDelegateSwizzler proxyOriginalDelegate]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULAppDelegateSwizzler sharedApplication]", + "coverage": 0.8333333333333334 + }, + { + "name": "+[GULAppDelegateSwizzler createSubclassWithObject:]", + "coverage": 0.7172413793103448 + }, + { + "name": "+[GULAppDelegateSwizzler interceptors]", + "coverage": 1 + }, + { + "name": "__38+[GULAppDelegateSwizzler interceptors]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULAppDelegateSwizzler addInstanceMethodWithSelector:fromClass:toClass:]", + "coverage": 1 + }, + { + "name": "+[GULAppDelegateSwizzler addInstanceMethodWithDestinationSelector:withImplementationFromSourceSelector:fromClass:toClass:]", + "coverage": 0.5 + }, + { + "name": "+[GULAppDelegateSwizzler implementationOfMethodSelector:fromClass:]", + "coverage": 1 + }, + { + "name": "+[GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:callback:]", + "coverage": 0 + }, + { + "name": "__72+[GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:callback:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULAppDelegateSwizzler fakeDescription]", + "coverage": 1 + }, + { + "name": "-[GULAppDelegateSwizzler application:openURL:options:]", + "coverage": 0 + }, + { + "name": "__54-[GULAppDelegateSwizzler application:openURL:options:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULAppDelegateSwizzler application:openURL:sourceApplication:annotation:]", + "coverage": 0 + }, + { + "name": "__75-[GULAppDelegateSwizzler application:openURL:sourceApplication:annotation:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULAppDelegateSwizzler application:handleEventsForBackgroundURLSession:completionHandler:]", + "coverage": 0 + }, + { + "name": "__92-[GULAppDelegateSwizzler application:handleEventsForBackgroundURLSession:completionHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULAppDelegateSwizzler application:continueUserActivity:restorationHandler:]", + "coverage": 0 + }, + { + "name": "__78-[GULAppDelegateSwizzler application:continueUserActivity:restorationHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[GULAppDelegateSwizzler proxyAppDelegate:]", + "coverage": 0.5625 + }, + { + "name": "+[GULAppDelegateSwizzler correctAppDelegateProxyKey]", + "coverage": 1 + }, + { + "name": "+[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]", + "coverage": 0 + } + ] + }, + { + "name": "GULMutableDictionary.m", + "coverage": 0.5609756097560976, + "type": "objc", + "functions": [ + { + "name": "-[GULMutableDictionary init]", + "coverage": 1 + }, + { + "name": "-[GULMutableDictionary description]", + "coverage": 0 + }, + { + "name": "__35-[GULMutableDictionary description]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULMutableDictionary objectForKey:]", + "coverage": 1 + }, + { + "name": "__37-[GULMutableDictionary objectForKey:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GULMutableDictionary setObject:forKey:]", + "coverage": 1 + }, + { + "name": "__41-[GULMutableDictionary setObject:forKey:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GULMutableDictionary removeObjectForKey:]", + "coverage": 0 + }, + { + "name": "__43-[GULMutableDictionary removeObjectForKey:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULMutableDictionary removeAllObjects]", + "coverage": 0 + }, + { + "name": "__40-[GULMutableDictionary removeAllObjects]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GULMutableDictionary count]", + "coverage": 1 + }, + { + "name": "__29-[GULMutableDictionary count]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GULMutableDictionary objectForKeyedSubscript:]", + "coverage": 1 + }, + { + "name": "-[GULMutableDictionary setObject:forKeyedSubscript:]", + "coverage": 1 + }, + { + "name": "-[GULMutableDictionary dictionary]", + "coverage": 0 + }, + { + "name": "__34-[GULMutableDictionary dictionary]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "GULAppEnvironmentUtil.m", + "coverage": 0.5681818181818182, + "type": "objc", + "functions": [ + { + "name": "IsAppEncrypted", + "coverage": 0.7209302325581395 + }, + { + "name": "HasSCInfoFolder", + "coverage": 0 + }, + { + "name": "HasEmbeddedMobileProvision", + "coverage": 0 + }, + { + "name": "+[GULAppEnvironmentUtil isFromAppStore]", + "coverage": 0.3548387096774194 + }, + { + "name": "__39+[GULAppEnvironmentUtil isFromAppStore]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULAppEnvironmentUtil isAppStoreReceiptSandbox]", + "coverage": 0 + }, + { + "name": "+[GULAppEnvironmentUtil isSimulator]", + "coverage": 1 + }, + { + "name": "+[GULAppEnvironmentUtil deviceModel]", + "coverage": 1 + }, + { + "name": "__36+[GULAppEnvironmentUtil deviceModel]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULAppEnvironmentUtil systemVersion]", + "coverage": 1 + }, + { + "name": "+[GULAppEnvironmentUtil isAppExtension]", + "coverage": 1 + } + ] + }, + { + "name": "GULReachabilityChecker.m", + "coverage": 0.58, + "type": "objc", + "functions": [ + { + "name": "-[GULReachabilityChecker reachabilityApi]", + "coverage": 0 + }, + { + "name": "-[GULReachabilityChecker setReachabilityApi:]", + "coverage": 0 + }, + { + "name": "-[GULReachabilityChecker isActive]", + "coverage": 0 + }, + { + "name": "-[GULReachabilityChecker setReachabilityDelegate:]", + "coverage": 0.5 + }, + { + "name": "-[GULReachabilityChecker initWithReachabilityDelegate:withHost:]", + "coverage": 0.7222222222222222 + }, + { + "name": "-[GULReachabilityChecker dealloc]", + "coverage": 1 + }, + { + "name": "-[GULReachabilityChecker start]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[GULReachabilityChecker stop]", + "coverage": 1 + }, + { + "name": "-[GULReachabilityChecker statusForFlags:]", + "coverage": 0.6190476190476191 + }, + { + "name": "-[GULReachabilityChecker reachabilityFlagsChanged:]", + "coverage": 0.95 + }, + { + "name": "ReachabilityCallback", + "coverage": 1 + }, + { + "name": "GULReachabilityStatusString", + "coverage": 0 + } + ] + }, + { + "name": "GULSwizzler.m", + "coverage": 0.7844036697247706, + "type": "objc", + "functions": [ + { + "name": "GetGULSwizzlingQueue", + "coverage": 1 + }, + { + "name": "__GetGULSwizzlingQueue_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULSwizzler swizzleClass:selector:isClassSelector:withBlock:]", + "coverage": 1 + }, + { + "name": "__63+[GULSwizzler swizzleClass:selector:isClassSelector:withBlock:]_block_invoke", + "coverage": 0.9512195121951219 + }, + { + "name": "+[GULSwizzler unswizzleClass:selector:isClassSelector:]", + "coverage": 1 + }, + { + "name": "__55+[GULSwizzler unswizzleClass:selector:isClassSelector:]_block_invoke", + "coverage": 0.9047619047619048 + }, + { + "name": "+[GULSwizzler originalImplementationForClass:selector:isClassSelector:]", + "coverage": 0 + }, + { + "name": "__71+[GULSwizzler originalImplementationForClass:selector:isClassSelector:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[GULSwizzler currentImplementationForClass:selector:isClassSelector:]", + "coverage": 0.92 + }, + { + "name": "__70+[GULSwizzler currentImplementationForClass:selector:isClassSelector:]_block_invoke", + "coverage": 0.8125 + }, + { + "name": "+[GULSwizzler selector:existsInClass:isClassSelector:]", + "coverage": 0 + }, + { + "name": "+[GULSwizzler ivarObjectsForObject:]", + "coverage": 0 + } + ] + }, + { + "name": "GULSwizzlingCache.m", + "coverage": 0.8157894736842105, + "type": "objc", + "functions": [ + { + "name": "+[GULSwizzlingCache sharedInstance]", + "coverage": 1 + }, + { + "name": "__35+[GULSwizzlingCache sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache init]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache dealloc]", + "coverage": 0 + }, + { + "name": "-[GULSwizzlingCache cacheCurrentIMP:forNewIMP:forClass:withSelector:]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache cachedIMPForClass:withSelector:]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache clearCacheForSwizzledIMP:selector:aClass:]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache originalIMPOfCurrentIMP:]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache clearCache]", + "coverage": 0 + }, + { + "name": "-[GULSwizzlingCache originalImps]", + "coverage": 0 + }, + { + "name": "-[GULSwizzlingCache newToOriginalImps]", + "coverage": 0 + } + ] + }, + { + "name": "GULLogger.m", + "coverage": 0.8333333333333334, + "type": "objc", + "functions": [ + { + "name": "GULLoggerInitializeASL", + "coverage": 1 + }, + { + "name": "__GULLoggerInitializeASL_block_invoke", + "coverage": 1 + }, + { + "name": "GULLoggerEnableSTDERR", + "coverage": 0 + }, + { + "name": "GULLoggerForceDebug", + "coverage": 1 + }, + { + "name": "GULSetLoggerLevel", + "coverage": 0.6470588235294118 + }, + { + "name": "__GULSetLoggerLevel_block_invoke", + "coverage": 1 + }, + { + "name": "GULIsLoggableLevel", + "coverage": 0.7142857142857143 + }, + { + "name": "GULResetLogger", + "coverage": 1 + }, + { + "name": "getGULLoggerClient", + "coverage": 0 + }, + { + "name": "getGULClientQueue", + "coverage": 0 + }, + { + "name": "getGULLoggerDebugMode", + "coverage": 0 + }, + { + "name": "GULLoggerRegisterVersion", + "coverage": 1 + }, + { + "name": "GULLogBasic", + "coverage": 1 + }, + { + "name": "__GULLogBasic_block_invoke", + "coverage": 1 + }, + { + "name": "Definition at 182:46", + "coverage": 1 + }, + { + "name": "+[GULLoggerWrapper logWithLevel:withService:withCode:withMessage:withArgs:]", + "coverage": 0 + } + ] + } + ] + }, + { + "name": "GoogleUtilities.framework", + "coverage": 0.4294416243654822, + "files": [ + { + "name": "GULRuntimeDiff.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "IsEqual", + "coverage": 0 + }, + { + "name": "-[GULRuntimeDiff hash]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeDiff isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeDiff description]", + "coverage": 0 + } + ] + }, + { + "name": "GULRuntimeStateHelper.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "+[GULRuntimeStateHelper snapshotCache]", + "coverage": 0 + }, + { + "name": "__38+[GULRuntimeStateHelper snapshotCache]_block_invoke", + "coverage": 0 + }, + { + "name": "+[GULRuntimeStateHelper captureRuntimeState]", + "coverage": 0 + }, + { + "name": "+[GULRuntimeStateHelper captureRuntimeStateOfClasses:]", + "coverage": 0 + }, + { + "name": "+[GULRuntimeStateHelper diffBetween:secondSnapshot:]", + "coverage": 0 + } + ] + }, + { + "name": "GULRuntimeSnapshot.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[GULRuntimeSnapshot init]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot initWithClasses:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot hash]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot description]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot capture]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeSnapshot diff:]", + "coverage": 0 + }, + { + "name": "__27-[GULRuntimeSnapshot diff:]_block_invoke", + "coverage": 0 + }, + { + "name": "__27-[GULRuntimeSnapshot diff:]_block_invoke.34", + "coverage": 0 + }, + { + "name": "__27-[GULRuntimeSnapshot diff:]_block_invoke.43", + "coverage": 0 + } + ] + }, + { + "name": "GULProxy.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[GULProxy initWithDelegate:]", + "coverage": 0 + }, + { + "name": "+[GULProxy proxyWithDelegate:]", + "coverage": 0 + }, + { + "name": "-[GULProxy forwardingTargetForSelector:]", + "coverage": 0 + }, + { + "name": "-[GULProxy forwardInvocation:]", + "coverage": 0 + }, + { + "name": "-[GULProxy methodSignatureForSelector:]", + "coverage": 0 + }, + { + "name": "-[GULProxy respondsToSelector:]", + "coverage": 0 + }, + { + "name": "-[GULProxy isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULProxy hash]", + "coverage": 0 + }, + { + "name": "-[GULProxy superclass]", + "coverage": 0 + }, + { + "name": "-[GULProxy class]", + "coverage": 0 + }, + { + "name": "-[GULProxy isKindOfClass:]", + "coverage": 0 + }, + { + "name": "-[GULProxy isMemberOfClass:]", + "coverage": 0 + }, + { + "name": "-[GULProxy conformsToProtocol:]", + "coverage": 0 + }, + { + "name": "-[GULProxy isProxy]", + "coverage": 0 + }, + { + "name": "-[GULProxy description]", + "coverage": 0 + }, + { + "name": "-[GULProxy debugDescription]", + "coverage": 0 + } + ] + }, + { + "name": "GULRuntimeClassDiff.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "IsEqual", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassDiff hash]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassDiff isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassDiff description]", + "coverage": 0 + } + ] + }, + { + "name": "GULRuntimeClassSnapshot.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[GULRuntimeClassSnapshot init]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot initWithClass:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot capture]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot diff:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot hash]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot isEqual:]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot description]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot captureProperties]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot captureSelectorsAndImps]", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot computeDiffOfProperties:withClassDiff:]", + "coverage": 0 + }, + { + "name": "__65-[GULRuntimeClassSnapshot computeDiffOfProperties:withClassDiff:]_block_invoke", + "coverage": 0 + }, + { + "name": "__65-[GULRuntimeClassSnapshot computeDiffOfProperties:withClassDiff:]_block_invoke.46", + "coverage": 0 + }, + { + "name": "-[GULRuntimeClassSnapshot computeDiffOfSelectorsAndImps:withClassDiff:]", + "coverage": 0 + }, + { + "name": "__71-[GULRuntimeClassSnapshot computeDiffOfSelectorsAndImps:withClassDiff:]_block_invoke", + "coverage": 0 + }, + { + "name": "__71-[GULRuntimeClassSnapshot computeDiffOfSelectorsAndImps:withClassDiff:]_block_invoke.57", + "coverage": 0 + }, + { + "name": "__71-[GULRuntimeClassSnapshot computeDiffOfSelectorsAndImps:withClassDiff:]_block_invoke.63", + "coverage": 0 + } + ] + }, + { + "name": "GULAppEnvironmentUtil.m", + "coverage": 0.5681818181818182, + "type": "objc", + "functions": [ + { + "name": "IsAppEncrypted", + "coverage": 0.7209302325581395 + }, + { + "name": "HasSCInfoFolder", + "coverage": 0 + }, + { + "name": "HasEmbeddedMobileProvision", + "coverage": 0 + }, + { + "name": "+[GULAppEnvironmentUtil isFromAppStore]", + "coverage": 0.3548387096774194 + }, + { + "name": "__39+[GULAppEnvironmentUtil isFromAppStore]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULAppEnvironmentUtil isAppStoreReceiptSandbox]", + "coverage": 0 + }, + { + "name": "+[GULAppEnvironmentUtil isSimulator]", + "coverage": 1 + }, + { + "name": "+[GULAppEnvironmentUtil deviceModel]", + "coverage": 1 + }, + { + "name": "__36+[GULAppEnvironmentUtil deviceModel]_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULAppEnvironmentUtil systemVersion]", + "coverage": 1 + }, + { + "name": "+[GULAppEnvironmentUtil isAppExtension]", + "coverage": 1 + } + ] + }, + { + "name": "GULSwizzler.m", + "coverage": 0.7844036697247706, + "type": "objc", + "functions": [ + { + "name": "GetGULSwizzlingQueue", + "coverage": 1 + }, + { + "name": "__GetGULSwizzlingQueue_block_invoke", + "coverage": 1 + }, + { + "name": "+[GULSwizzler swizzleClass:selector:isClassSelector:withBlock:]", + "coverage": 1 + }, + { + "name": "__63+[GULSwizzler swizzleClass:selector:isClassSelector:withBlock:]_block_invoke", + "coverage": 0.9512195121951219 + }, + { + "name": "+[GULSwizzler unswizzleClass:selector:isClassSelector:]", + "coverage": 1 + }, + { + "name": "__55+[GULSwizzler unswizzleClass:selector:isClassSelector:]_block_invoke", + "coverage": 0.9047619047619048 + }, + { + "name": "+[GULSwizzler originalImplementationForClass:selector:isClassSelector:]", + "coverage": 0 + }, + { + "name": "__71+[GULSwizzler originalImplementationForClass:selector:isClassSelector:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[GULSwizzler currentImplementationForClass:selector:isClassSelector:]", + "coverage": 0.92 + }, + { + "name": "__70+[GULSwizzler currentImplementationForClass:selector:isClassSelector:]_block_invoke", + "coverage": 0.8125 + }, + { + "name": "+[GULSwizzler selector:existsInClass:isClassSelector:]", + "coverage": 0 + }, + { + "name": "+[GULSwizzler ivarObjectsForObject:]", + "coverage": 0 + } + ] + }, + { + "name": "GULSwizzlingCache.m", + "coverage": 0.8157894736842105, + "type": "objc", + "functions": [ + { + "name": "+[GULSwizzlingCache sharedInstance]", + "coverage": 1 + }, + { + "name": "__35+[GULSwizzlingCache sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache init]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache dealloc]", + "coverage": 0 + }, + { + "name": "-[GULSwizzlingCache cacheCurrentIMP:forNewIMP:forClass:withSelector:]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache cachedIMPForClass:withSelector:]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache clearCacheForSwizzledIMP:selector:aClass:]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache originalIMPOfCurrentIMP:]", + "coverage": 1 + }, + { + "name": "-[GULSwizzlingCache clearCache]", + "coverage": 0 + }, + { + "name": "-[GULSwizzlingCache originalImps]", + "coverage": 0 + }, + { + "name": "-[GULSwizzlingCache newToOriginalImps]", + "coverage": 0 + } + ] + }, + { + "name": "GULLogger.m", + "coverage": 0.8333333333333334, + "type": "objc", + "functions": [ + { + "name": "GULLoggerInitializeASL", + "coverage": 1 + }, + { + "name": "__GULLoggerInitializeASL_block_invoke", + "coverage": 1 + }, + { + "name": "GULLoggerEnableSTDERR", + "coverage": 0 + }, + { + "name": "GULLoggerForceDebug", + "coverage": 1 + }, + { + "name": "GULSetLoggerLevel", + "coverage": 0.6470588235294118 + }, + { + "name": "__GULSetLoggerLevel_block_invoke", + "coverage": 1 + }, + { + "name": "GULIsLoggableLevel", + "coverage": 0.7142857142857143 + }, + { + "name": "GULResetLogger", + "coverage": 1 + }, + { + "name": "getGULLoggerClient", + "coverage": 0 + }, + { + "name": "getGULClientQueue", + "coverage": 0 + }, + { + "name": "getGULLoggerDebugMode", + "coverage": 0 + }, + { + "name": "GULLoggerRegisterVersion", + "coverage": 1 + }, + { + "name": "GULLogBasic", + "coverage": 1 + }, + { + "name": "__GULLogBasic_block_invoke", + "coverage": 1 + }, + { + "name": "Definition at 182:46", + "coverage": 1 + }, + { + "name": "+[GULLoggerWrapper logWithLevel:withService:withCode:withMessage:withArgs:]", + "coverage": 0 + } + ] + } + ] + }, + { + "name": "InstanceID_Example_iOS.app", + "coverage": 0.8083930704898447, + "files": [ + { + "name": "FIRInstanceIDTokenDeleteOperation.m", + "coverage": 0.12643678160919541, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenDeleteOperation initWithAuthorizedEntity:scope:checkinPreferences:keyPair:action:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenDeleteOperation performTokenOperation]", + "coverage": 0 + }, + { + "name": "__58-[FIRInstanceIDTokenDeleteOperation performTokenOperation]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenDeleteOperation handleResponseWithData:response:error:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAppDelegate.m", + "coverage": 0.2413793103448276, + "type": "objc", + "functions": [ + { + "name": "-[FIRAppDelegate application:didFinishLaunchingWithOptions:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillResignActive:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidEnterBackground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationWillEnterForeground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidBecomeActive:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillTerminate:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceID+Private.m", + "coverage": 0.3333333333333333, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceID(Private) cachedCheckinPreferences]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID(Private) fetchCheckinInfoWithHandler:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID(Private) appInstanceID:]", + "coverage": 1 + } + ] + }, + { + "name": "NSError+FIRInstanceID.m", + "coverage": 0.375, + "type": "objc", + "functions": [ + { + "name": "-[NSError(FIRInstanceID) instanceIDErrorCode]", + "coverage": 0 + }, + { + "name": "+[NSError(FIRInstanceID) errorWithFIRInstanceIDErrorCode:]", + "coverage": 1 + }, + { + "name": "+[NSError(FIRInstanceID) errorWithFIRInstanceIDErrorCode:userInfo:]", + "coverage": 1 + }, + { + "name": "+[NSError(FIRInstanceID) FIRInstanceIDErrorMissingCheckin]", + "coverage": 0 + } + ] + }, + { + "name": "FIRViewController.m", + "coverage": 0.5, + "type": "objc", + "functions": [ + { + "name": "-[FIRViewController viewDidLoad]", + "coverage": 1 + }, + { + "name": "-[FIRViewController didReceiveMemoryWarning]", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDStringEncoding.m", + "coverage": 0.5308641975308642, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDStringEncoding rfc4648Base64WebsafeStringEncoding]", + "coverage": 1 + }, + { + "name": "lcm", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStringEncoding stringEncodingWithString:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStringEncoding initWithString:]", + "coverage": 0.6896551724137931 + }, + { + "name": "-[FIRInstanceIDStringEncoding description]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDStringEncoding doPad]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDStringEncoding setDoPad:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStringEncoding setPaddingChar:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStringEncoding encode:]", + "coverage": 0.8444444444444444 + }, + { + "name": "-[FIRInstanceIDStringEncoding decode:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDTokenManager.m", + "coverage": 0.7067039106145251, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenManager init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager configureTokenOperations]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke.52", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke_2.53", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager cachedTokenInfoWithAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager deleteTokenWithAuthorizedEntity:scope:keyPair:handler:]", + "coverage": 0.9166666666666666 + }, + { + "name": "__83-[FIRInstanceIDTokenManager deleteTokenWithAuthorizedEntity:scope:keyPair:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__83-[FIRInstanceIDTokenManager deleteTokenWithAuthorizedEntity:scope:keyPair:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]", + "coverage": 0 + }, + { + "name": "__64-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__64-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]_block_invoke.86", + "coverage": 0 + }, + { + "name": "__64-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager deleteAllTokensLocallyWithHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager stopAllTokenOperations]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager store:didDeleteFCMScopedTokensForCheckin:]", + "coverage": 0 + }, + { + "name": "__70-[FIRInstanceIDTokenManager store:didDeleteFCMScopedTokensForCheckin:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager createFetchOperationWithAuthorizedEntity:scope:options:keyPair:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager createDeleteOperationWithAuthorizedEntity:scope:checkinPreferences:keyPair:action:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager checkForTokenRefreshPolicy]", + "coverage": 0.8125 + }, + { + "name": "-[FIRInstanceIDTokenManager updateTokensToAPNSDeviceToken:isSandbox:]", + "coverage": 0.9333333333333333 + } + ] + }, + { + "name": "FIRInstanceIDLogger.m", + "coverage": 0.72, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDLogger formatMessageCode:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDLogger logFuncDebug:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDLogger logFuncInfo:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDLogger logFuncNotice:messageCode:msg:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDLogger logFuncWarning:messageCode:msg:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDLogger logFuncError:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "FIRInstanceIDSharedLogger", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDSharedLogger_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceID.m", + "coverage": 0.7266414141414141, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDResult copyWithZone:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID instanceID]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIRInstanceID initPrivately]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID instanceIDForTests]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID token]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID instanceIDWithHandler:]", + "coverage": 1 + }, + { + "name": "__39-[FIRInstanceID instanceIDWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__39-[FIRInstanceID instanceIDWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID cachedTokenIfAvailable]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID setDefaultFCMToken:]", + "coverage": 0.8666666666666667 + }, + { + "name": "-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]", + "coverage": 0.8584905660377359 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke.163", + "coverage": 0.9302325581395349 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke_2.171", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]", + "coverage": 0.84375 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke", + "coverage": 0.8 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke.195", + "coverage": 0.8695652173913043 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke_2.196", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID asyncLoadKeyPairWithHandler:]", + "coverage": 1 + }, + { + "name": "__45-[FIRInstanceID asyncLoadKeyPairWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__45-[FIRInstanceID asyncLoadKeyPairWithHandler:]_block_invoke_2", + "coverage": 0.7222222222222222 + }, + { + "name": "-[FIRInstanceID getIDWithHandler:]", + "coverage": 0.7837837837837838 + }, + { + "name": "__34-[FIRInstanceID getIDWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__34-[FIRInstanceID getIDWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__34-[FIRInstanceID getIDWithHandler:]_block_invoke.223", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID deleteIDWithHandler:]", + "coverage": 0.8333333333333334 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke", + "coverage": 0.5555555555555556 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke.240", + "coverage": 0 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke_2.241", + "coverage": 0 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke.250", + "coverage": 0.8 + }, + { + "name": "-[FIRInstanceID notifyIdentityReset]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID deleteIdentityWithHandler:]", + "coverage": 1 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke", + "coverage": 0.9137931034482759 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke_3", + "coverage": 0 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke_4", + "coverage": 0 + }, + { + "name": "+[FIRInstanceID load]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID componentsToRegister]", + "coverage": 1 + }, + { + "name": "__37+[FIRInstanceID componentsToRegister]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID configureWithApp:]", + "coverage": 0.4444444444444444 + }, + { + "name": "-[FIRInstanceID configureInstanceIDWithOptions:app:]", + "coverage": 0.7222222222222222 + }, + { + "name": "+[FIRInstanceID configureErrorWithReason:]", + "coverage": 0 + }, + { + "name": "+[FIRInstanceID exitWithReason:forFirebaseApp:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID didCompleteConfigure]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID isFCMAutoInitEnabled]", + "coverage": 0.8709677419354839 + }, + { + "name": "-[FIRInstanceID start]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FIRInstanceID setupTokenManager]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID setupKeyPairManager]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID setupNotificationListeners]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID maxRetryCountForDefaultToken]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID minIntervalForDefaultTokenRetry]", + "coverage": 0 + }, + { + "name": "+[FIRInstanceID maxRetryIntervalForDefaultTokenInSeconds]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID retryIntervalToFetchDefaultToken]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIRInstanceID fetchDefaultToken]", + "coverage": 1 + }, + { + "name": "__34-[FIRInstanceID fetchDefaultToken]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID defaultTokenWithHandler:]", + "coverage": 0.89 + }, + { + "name": "__41-[FIRInstanceID defaultTokenWithHandler:]_block_invoke", + "coverage": 0.8133333333333334 + }, + { + "name": "__41-[FIRInstanceID defaultTokenWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__41-[FIRInstanceID defaultTokenWithHandler:]_block_invoke.406", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID notifyAPNSTokenIsSet:]", + "coverage": 0 + }, + { + "name": "__38-[FIRInstanceID notifyAPNSTokenIsSet:]_block_invoke", + "coverage": 0 + }, + { + "name": "__38-[FIRInstanceID notifyAPNSTokenIsSet:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID isSandboxApp]", + "coverage": 1 + }, + { + "name": "__29-[FIRInstanceID isSandboxApp]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID isProductionApp]", + "coverage": 0.22556390977443608 + }, + { + "name": "-[FIRInstanceID logAPNSConfigurationError:]", + "coverage": 0.875 + } + ] + }, + { + "name": "FIRInstanceIDTokenFetchOperation.m", + "coverage": 0.75, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenFetchOperation initWithAuthorizedEntity:scope:options:checkinPreferences:keyPair:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenFetchOperation performTokenOperation]", + "coverage": 0.9206349206349206 + }, + { + "name": "__57-[FIRInstanceIDTokenFetchOperation performTokenOperation]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenFetchOperation handleResponseWithData:response:error:]", + "coverage": 0.5454545454545454 + }, + { + "name": "__74-[FIRInstanceIDTokenFetchOperation handleResponseWithData:response:error:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenFetchOperation parseFetchTokenResponse:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDVersionUtilities.m", + "coverage": 0.775, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDParseCurrentLibraryVersion", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDParseCurrentLibraryVersion_block_invoke", + "coverage": 0.9285714285714286 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersion", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionMajor", + "coverage": 0 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionMinor", + "coverage": 0 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionPatch", + "coverage": 0 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionIsBeta", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDURLQueryItem.m", + "coverage": 0.78125, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDURLQueryItem queryItemWithName:value:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDURLQueryItem initWithName:value:]", + "coverage": 1 + }, + { + "name": "FIRInstanceIDQueryFromQueryItems", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "FIRInstanceIDBackupExcludedPlist.m", + "coverage": 0.7816901408450704, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDBackupExcludedPlist initWithFileName:subDirectory:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist writeDictionary:error:]", + "coverage": 0.696969696969697 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist deleteFile:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist contentAsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist moveToApplicationSupportSubDirectory:]", + "coverage": 0.5428571428571428 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist doesFileExist]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist plistDirectory]", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist plistPathInDirectory:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist pathWithName:inDirectory:]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist doesFileExistInDirectory:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist supportedDirectory]", + "coverage": 1 + } + ] + }, + { + "name": "FIRDependency.m", + "coverage": 0.7857142857142857, + "type": "objc", + "functions": [ + { + "name": "+[FIRDependency dependencyWithProtocol:]", + "coverage": 0 + }, + { + "name": "+[FIRDependency dependencyWithProtocol:isRequired:]", + "coverage": 1 + }, + { + "name": "-[FIRDependency initWithProtocol:isRequired:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRBundleUtil.m", + "coverage": 0.7872340425531915, + "type": "objc", + "functions": [ + { + "name": "+[FIRBundleUtil relevantBundles]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil optionsDictionaryPathWithResourceName:andFileType:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil relevantURLSchemes]", + "coverage": 0 + }, + { + "name": "+[FIRBundleUtil hasBundleIdentifierPrefix:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil bundleIdentifierByRemovingLastPartFrom:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinStore.m", + "coverage": 0.7874396135265701, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDCheckinStore initWithCheckinPlistFileName:subDirectoryName:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore initWithCheckinPlist:keychain:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore hasCheckinPlist]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore bundleIdentifierForKeychainAccount]", + "coverage": 1 + }, + { + "name": "__63-[FIRInstanceIDCheckinStore bundleIdentifierForKeychainAccount]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore saveCheckinPreferences:handler:]", + "coverage": 0.5573770491803278 + }, + { + "name": "__60-[FIRInstanceIDCheckinStore saveCheckinPreferences:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore removeCheckinPreferencesWithHandler:]", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceIDCheckinStore removeCheckinPreferencesWithHandler:]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRInstanceIDCheckinStore cachedCheckinPreferences]", + "coverage": 0.7073170731707317 + }, + { + "name": "-[FIRInstanceIDCheckinStore migrateCheckinItemIfNeeded]", + "coverage": 1 + } + ] + }, + { + "name": "FIRConfiguration.m", + "coverage": 0.8076923076923077, + "type": "objc", + "functions": [ + { + "name": "+[FIRConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__34+[FIRConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration init]", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration setLoggerLevel:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRLogger.m", + "coverage": 0.8118811881188119, + "type": "objc", + "functions": [ + { + "name": "FIRLoggerInitializeASL", + "coverage": 1 + }, + { + "name": "__FIRLoggerInitializeASL_block_invoke", + "coverage": 0.9333333333333333 + }, + { + "name": "FIRSetAnalyticsDebugMode", + "coverage": 0 + }, + { + "name": "FIRSetLoggerLevel", + "coverage": 0 + }, + { + "name": "FIRResetLogger", + "coverage": 1 + }, + { + "name": "FIRSetLoggerUserDefaults", + "coverage": 1 + }, + { + "name": "FIRIsLoggableLevel", + "coverage": 0 + }, + { + "name": "FIRLogBasic", + "coverage": 1 + }, + { + "name": "Definition at 158:95", + "coverage": 1 + }, + { + "name": "+[FIRLoggerWrapper logWithLevel:withService:withCode:withMessage:withArgs:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDTokenOperation.m", + "coverage": 0.8323353293413174, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDTokenOperation sharedURLSession]", + "coverage": 0 + }, + { + "name": "__47+[FIRInstanceIDTokenOperation sharedURLSession]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenOperation initWithAction:forAuthorizedEntity:scope:options:checkinPreferences:keyPair:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation addCompletionHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation isAsynchronous]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenOperation isExecuting]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation setExecuting:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation isFinished]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation setFinished:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation start]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FIRInstanceIDTokenOperation finishWithResult:token:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation cancel]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation performTokenOperation]", + "coverage": 0 + }, + { + "name": "+[FIRInstanceIDTokenOperation requestWithAuthHeader:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenOperation standardQueryItemsWithDeviceID:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation queryItemsWithKeyPair:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDKeyPairStore.m", + "coverage": 0.8351449275362319, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDKeyDataWithTag", + "coverage": 0.8333333333333334 + }, + { + "name": "FIRInstanceIDCachedKeyRefWithTag", + "coverage": 0.7777777777777778 + }, + { + "name": "FIRInstanceIDHasMigratedKeyPair", + "coverage": 1 + }, + { + "name": "FIRInstanceIDLegacyPublicTagWithSubtype", + "coverage": 1 + }, + { + "name": "FIRInstanceIDLegacyPrivateTagWithSubtype", + "coverage": 1 + }, + { + "name": "FIRInstanceIDPublicTagWithSubtype", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDPublicTagWithSubtype_block_invoke", + "coverage": 1 + }, + { + "name": "FIRInstanceIDPrivateTagWithSubtype", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDPrivateTagWithSubtype_block_invoke", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCreationTimeKeyWithSubtype", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore invalidateKeyPairsIfNeeded]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore hasCachedKeyPairs]", + "coverage": 0.29411764705882354 + }, + { + "name": "-[FIRInstanceIDKeyPairStore appIdentityWithError:]", + "coverage": 0.6 + }, + { + "name": "-[FIRInstanceIDKeyPairStore loadKeyPairWithError:]", + "coverage": 0.78125 + }, + { + "name": "-[FIRInstanceIDKeyPairStore generateAndSaveKeyWithSubtype:creationTime:error:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRInstanceIDKeyPairStore validCachedKeyPairWithSubtype:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore cachedKeyPairWithSubtype:error:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDKeyPairStore keyPairForPrivateKeyTag:publicKeyTag:error:]", + "coverage": 0.8387096774193549 + }, + { + "name": "-[FIRInstanceIDKeyPairStore migrateKeyPairCacheIfNeededWithHandler:]", + "coverage": 0.8771929824561403 + }, + { + "name": "__68-[FIRInstanceIDKeyPairStore migrateKeyPairCacheIfNeededWithHandler:]_block_invoke", + "coverage": 0.8636363636363636 + }, + { + "name": "__68-[FIRInstanceIDKeyPairStore migrateKeyPairCacheIfNeededWithHandler:]_block_invoke_2", + "coverage": 0.6428571428571429 + }, + { + "name": "-[FIRInstanceIDKeyPairStore updateKeyRef:withTag:handler:]", + "coverage": 1 + }, + { + "name": "__58-[FIRInstanceIDKeyPairStore updateKeyRef:withTag:handler:]_block_invoke", + "coverage": 0.7619047619047619 + }, + { + "name": "__58-[FIRInstanceIDKeyPairStore updateKeyRef:withTag:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore deleteSavedKeyPairWithSubtype:handler:]", + "coverage": 0.9375 + }, + { + "name": "__67-[FIRInstanceIDKeyPairStore deleteSavedKeyPairWithSubtype:handler:]_block_invoke", + "coverage": 0.76 + }, + { + "name": "+[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:publicTag:handler:]", + "coverage": 1 + }, + { + "name": "__75+[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:publicTag:handler:]_block_invoke", + "coverage": 0.7619047619047619 + }, + { + "name": "__75+[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:publicTag:handler:]_block_invoke_2", + "coverage": 0.5454545454545454 + }, + { + "name": "-[FIRInstanceIDKeyPairStore removeKeyPairCreationTimePlistWithError:]", + "coverage": 0.5 + }, + { + "name": "+[FIRInstanceIDKeyPairStore keyStoreFileName]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDUtilities.m", + "coverage": 0.8547008547008547, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDRegisterServer", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentTimestampInSeconds", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentTimestampInMilliseconds", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentAppVersion", + "coverage": 1 + }, + { + "name": "FIRInstanceIDAppIdentifier", + "coverage": 1 + }, + { + "name": "FIRInstanceIDFirebaseAppID", + "coverage": 1 + }, + { + "name": "FIRInstanceIDDeviceModel", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDDeviceModel_block_invoke", + "coverage": 1 + }, + { + "name": "FIRInstanceIDOperatingSystemVersion", + "coverage": 1 + }, + { + "name": "FIRInstanceIDHasLocaleChanged", + "coverage": 1 + }, + { + "name": "FIRInstanceIDIsValidGCMScope", + "coverage": 1 + }, + { + "name": "FIRInstanceIDStringForAPNSDeviceToken", + "coverage": 1 + }, + { + "name": "FIRInstanceIDAPNSTupleStringForTokenAndServerType", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentGCMVersion", + "coverage": 0.6 + }, + { + "name": "FIRInstanceIDCurrentLocale", + "coverage": 0.4583333333333333 + } + ] + }, + { + "name": "FIRInstanceIDStore.m", + "coverage": 0.8579234972677595, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDStore initWithDelegate:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore initWithCheckinStore:tokenStore:delegate:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStore hasSubDirectory:]", + "coverage": 0.8181818181818182 + }, + { + "name": "+[FIRInstanceIDStore supportedDirectory]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStore pathForSupportSubDirectory:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStore createSubDirectory:]", + "coverage": 0.6 + }, + { + "name": "+[FIRInstanceIDStore removeSubDirectory:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore resetCredentialsIfNeeded]", + "coverage": 1 + }, + { + "name": "__46-[FIRInstanceIDStore resetCredentialsIfNeeded]_block_invoke", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRInstanceIDStore tokenInfoWithAuthorizedEntity:scope:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRInstanceIDStore cachedTokenInfos]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore saveTokenInfo:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore removeCachedTokenWithAuthorizedEntity:scope:]", + "coverage": 0.4444444444444444 + }, + { + "name": "-[FIRInstanceIDStore removeAllCachedTokensWithHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore saveCheckinPreferences:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore cachedCheckinPreferences]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore removeCheckinPreferencesWithHandler:]", + "coverage": 1 + }, + { + "name": "__58-[FIRInstanceIDStore removeCheckinPreferencesWithHandler:]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FIRApp.m", + "coverage": 0.8848, + "type": "objc", + "functions": [ + { + "name": "+[FIRApp configure]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithOptions:]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithName:options:]", + "coverage": 0.9333333333333333 + }, + { + "name": "+[FIRApp defaultApp]", + "coverage": 1 + }, + { + "name": "+[FIRApp appNamed:]", + "coverage": 1 + }, + { + "name": "+[FIRApp allApps]", + "coverage": 1 + }, + { + "name": "+[FIRApp resetApps]", + "coverage": 1 + }, + { + "name": "-[FIRApp deleteApp:]", + "coverage": 0.7916666666666666 + }, + { + "name": "+[FIRApp addAppToAppDictionary:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRApp initInstanceWithName:options:]", + "coverage": 1 + }, + { + "name": "-[FIRApp configureCore]", + "coverage": 0.7962962962962963 + }, + { + "name": "-[FIRApp options]", + "coverage": 1 + }, + { + "name": "-[FIRApp setDataCollectionDefaultEnabled:]", + "coverage": 0.9354838709677419 + }, + { + "name": "-[FIRApp isDataCollectionDefaultEnabled]", + "coverage": 1 + }, + { + "name": "+[FIRApp sendNotificationsToSDKs:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForMissingOptions]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForSubspecConfigurationFailureWithDomain:errorCode:service:reason:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForInvalidAppID]", + "coverage": 0 + }, + { + "name": "+[FIRApp isDefaultAppConfigured]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerLibrary:withVersion:]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerInternalLibrary:withName:withVersion:]", + "coverage": 1 + }, + { + "name": "__55+[FIRApp registerInternalLibrary:withName:withVersion:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRApp firebaseUserAgent]", + "coverage": 1 + }, + { + "name": "-[FIRApp checkExpectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp isAppIDValid]", + "coverage": 1 + }, + { + "name": "+[FIRApp validateAppID:]", + "coverage": 0.9473684210526315 + }, + { + "name": "+[FIRApp actualBundleID]", + "coverage": 0 + }, + { + "name": "+[FIRApp validateAppIDFormat:withVersion:]", + "coverage": 0.8450704225352113 + }, + { + "name": "+[FIRApp validateAppIDFingerprint:withVersion:]", + "coverage": 0.8620689655172413 + }, + { + "name": "-[FIRApp expectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp clearDataCollectionSwitchFromUserDefaults]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromUserDefaultsForApp:]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromPlist]", + "coverage": 1 + }, + { + "name": "__43+[FIRApp readDataCollectionSwitchFromPlist]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRApp sendLogsWithServiceName:version:error:]", + "coverage": 0.2631578947368421 + } + ] + }, + { + "name": "FIRInstanceIDTokenInfo.m", + "coverage": 0.889763779527559, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenInfo initWithAuthorizedEntity:scope:token:appVersion:firebaseAppID:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenInfo isFresh]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenInfo initWithCoder:]", + "coverage": 0.78125 + }, + { + "name": "-[FIRInstanceIDTokenInfo encodeWithCoder:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinPreferences.m", + "coverage": 0.8913043478260869, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDCheckinPreferences checkinPlistContents]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences hasCheckinInfo]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences hasValidCheckinInfo]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences setHasPreCachedAuthCredentials:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinService.m", + "coverage": 0.896414342629482, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDCheckinService init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinService dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinService checkinWithExistingCheckin:completion:]", + "coverage": 0.9649122807017544 + }, + { + "name": "__69-[FIRInstanceIDCheckinService checkinWithExistingCheckin:completion:]_block_invoke", + "coverage": 0.7105263157894737 + }, + { + "name": "-[FIRInstanceIDCheckinService stopFetching]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinService checkinParametersWithExistingCheckin:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDCheckinService setCheckinTestBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDAPNSInfo.m", + "coverage": 0.9090909090909091, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDAPNSInfo initWithDeviceToken:isSandbox:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAPNSInfo initWithTokenOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAPNSInfo initWithCoder:]", + "coverage": 0.75 + }, + { + "name": "-[FIRInstanceIDAPNSInfo encodeWithCoder:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAPNSInfo isEqualToAPNSInfo:]", + "coverage": 0.7142857142857143 + } + ] + }, + { + "name": "FIRInstanceIDAuthService.m", + "coverage": 0.9166666666666666, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDAuthService initWithCheckinService:store:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService initWithStore:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService scheduleCheckin:]", + "coverage": 0.7 + }, + { + "name": "-[FIRInstanceIDAuthService startCheckinTimerWithDuration:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService clearScheduledCheckinTimer]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService onScheduledCheckinTimerFired:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService performScheduledCheckin]", + "coverage": 1 + }, + { + "name": "__51-[FIRInstanceIDAuthService performScheduledCheckin]_block_invoke", + "coverage": 1 + }, + { + "name": "__51-[FIRInstanceIDAuthService performScheduledCheckin]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService calculateNextCheckinRetryIntervalInSeconds]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FIRInstanceIDAuthService hasValidCheckinInfo]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]", + "coverage": 1 + }, + { + "name": "__56-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__56-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__56-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService checkinPreferences]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService stopCheckinRequest]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService resetCheckinWithHandler:]", + "coverage": 0 + }, + { + "name": "__52-[FIRInstanceIDAuthService resetCheckinWithHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDAuthService notifyCheckinHandlersWithCheckin:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService cachedCheckinMatchesCheckin:]", + "coverage": 0.5714285714285714 + } + ] + }, + { + "name": "FIRInstanceIDKeychain.m", + "coverage": 0.9227053140096618, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDKeychain sharedInstance]", + "coverage": 1 + }, + { + "name": "__39+[FIRInstanceIDKeychain sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain itemWithQuery:]", + "coverage": 1 + }, + { + "name": "__39-[FIRInstanceIDKeychain itemWithQuery:]_block_invoke", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRInstanceIDKeychain removeItemWithQuery:handler:]", + "coverage": 1 + }, + { + "name": "__53-[FIRInstanceIDKeychain removeItemWithQuery:handler:]_block_invoke", + "coverage": 0.8260869565217391 + }, + { + "name": "__53-[FIRInstanceIDKeychain removeItemWithQuery:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain addItemWithQuery:handler:]", + "coverage": 1 + }, + { + "name": "__50-[FIRInstanceIDKeychain addItemWithQuery:handler:]_block_invoke", + "coverage": 0.6470588235294118 + }, + { + "name": "__50-[FIRInstanceIDKeychain addItemWithQuery:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain generateKeyPairWithPrivateTag:publicTag:]", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceIDKeychain generateKeyPairWithPrivateTag:publicTag:]_block_invoke", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "FIRInstanceIDAuthKeyChain.m", + "coverage": 0.9405405405405406, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDAuthKeychain initWithIdentifier:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDAuthKeychain keychainQueryForService:account:generic:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain keychainQueryForService:account:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain itemsMatchingService:account:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain dataForService:account:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain removeItemsMatchingService:account:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain setData:forService:accessibility:account:handler:]", + "coverage": 0.9183673469387755 + }, + { + "name": "__78-[FIRInstanceIDAuthKeychain setData:forService:accessibility:account:handler:]_block_invoke", + "coverage": 0.72 + } + ] + }, + { + "name": "FIROptions.m", + "coverage": 0.9489795918367347, + "type": "objc", + "functions": [ + { + "name": "+[FIROptions defaultOptions]", + "coverage": 0.8461538461538461 + }, + { + "name": "+[FIROptions initialize]", + "coverage": 1 + }, + { + "name": "+[FIROptions defaultOptionsDictionary]", + "coverage": 1 + }, + { + "name": "+[FIROptions plistFilePathWithName:]", + "coverage": 1 + }, + { + "name": "+[FIROptions resetDefaultOptions]", + "coverage": 1 + }, + { + "name": "-[FIROptions initInternalWithOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithContentsOfFile:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithGoogleAppID:GCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions APIKey]", + "coverage": 1 + }, + { + "name": "-[FIROptions checkEditingLocked]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAPIKey:]", + "coverage": 1 + }, + { + "name": "-[FIROptions clientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions trackingID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setTrackingID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions GCMSenderID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions projectID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setProjectID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions androidClientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAndroidClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions googleAppID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGoogleAppID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions libraryVersionID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setLibraryVersionID:]", + "coverage": 0 + }, + { + "name": "-[FIROptions databaseURL]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDatabaseURL:]", + "coverage": 1 + }, + { + "name": "-[FIROptions storageBucket]", + "coverage": 1 + }, + { + "name": "-[FIROptions setStorageBucket:]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDeepLinkURLScheme:]", + "coverage": 1 + }, + { + "name": "-[FIROptions bundleID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionaryWithInfoDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIROptions isMeasurementEnabled]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIROptions isAnalyticsCollectionExpicitlySet]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionEnabled]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionDeactivated]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsEnabled]", + "coverage": 0 + }, + { + "name": "-[FIROptions isSignInEnabled]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAnalyticsConfiguration.m", + "coverage": 0.9545454545454546, + "type": "objc", + "functions": [ + { + "name": "+[FIRAnalyticsConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__43+[FIRAnalyticsConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration postNotificationName:value:]", + "coverage": 0.75 + }, + { + "name": "-[FIRAnalyticsConfiguration setMinimumSessionInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setSessionTimeoutInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:persistSetting:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinPreferences+Internal.m", + "coverage": 0.9692307692307692, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) preferencesFromKeychainContents:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) initWithDeviceID:secretToken:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) reset]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) updateWithCheckinPlistContents:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) checkinKeychainContent]", + "coverage": 0.75 + }, + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) checkinDeviceIDFromKeychainContent:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) checkinSecretFromKeychainContent:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) checkinKeychainContent:forIndex:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentContainer.m", + "coverage": 0.9818181818181818, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:]", + "coverage": 1 + }, + { + "name": "__55+[FIRComponentContainer registerAsComponentRegistrant:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:inSet:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:registrants:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer populateComponentsFromRegisteredClasses:forApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer instantiateInstanceForProtocol:withBlock:]", + "coverage": 0.9285714285714286 + }, + { + "name": "-[FIRComponentContainer instanceForProtocol:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer removeAllCachedInstances]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDKeyPair.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDKeyPair initWithPrivateKey:publicKey:publicKeyData:privateKeyData:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair isValid]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair publicKey]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair privateKey]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDTokenStore.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDTokenStore defaultStore]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore initWithKeychain:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenStore serviceKeyForAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore tokenInfoWithAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore cachedTokenInfos]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenStore tokenInfoFromKeychainItem:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore saveTokenInfo:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore removeTokenWithAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore removeAllTokensWithHandler:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentType.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentType instanceForProtocol:inContainer:]", + "coverage": 1 + } + ] + }, + { + "name": "main.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "main", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponent.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponent componentWithProtocol:creationBlock:]", + "coverage": 1 + }, + { + "name": "+[FIRComponent componentWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + }, + { + "name": "-[FIRComponent initWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDKeyPairUtilities.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDWebSafeBase64", + "coverage": 1 + }, + { + "name": "FIRInstanceIDSHA1", + "coverage": 1 + }, + { + "name": "FIRInstanceIDKeyPairQuery", + "coverage": 1 + }, + { + "name": "FIRInstanceIDAppIdentity", + "coverage": 1 + } + ] + }, + { + "name": "FIRAppAssociationRegistration.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRAppAssociationRegistration registeredObjectWithHost:key:creationBlock:]", + "coverage": 1 + } + ] + } + ] + }, + { + "name": "Messaging_Example_iOS.app", + "coverage": 0.6270501835985313, + "files": [ + { + "name": "GtalkExtensions.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GtalkGtalkExtensionsRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "+[GtalkRosterQuery descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkRosterItem descriptor]", + "coverage": 0 + }, + { + "name": "GtalkRosterItem_SubscriptionType_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkRosterItem_SubscriptionType_IsValidValue", + "coverage": 0 + }, + { + "name": "GtalkRosterItem_AskType_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkRosterItem_AskType_IsValidValue", + "coverage": 0 + }, + { + "name": "GtalkRosterItem_DisplayType_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkRosterItem_DisplayType_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GtalkRmqLastId descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkRmqAck descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkVCard descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkPhoto descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkChatRead descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkChatClosed descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkCapabilities descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkSharedStatus descriptor]", + "coverage": 0 + }, + { + "name": "GtalkSharedStatus_ShowType_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkSharedStatus_ShowType_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GtalkSharedStatus_StatusList descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkOtrQuery descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkOtrItem descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkIdle descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkPostAuthBatchQuery descriptor]", + "coverage": 0 + }, + { + "name": "GtalkPostAuthBatchQuery_CapabilitiesExtFlags_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkPostAuthBatchQuery_CapabilitiesExtFlags_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GtalkStreamAck descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkSelectiveAck descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "FIRMessagingTopicOperation.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "FIRMessagingSubscriptionsServer", + "coverage": 0 + }, + { + "name": "__FIRMessagingSubscriptionsServer_block_invoke", + "coverage": 0 + }, + { + "name": "+[FIRMessagingTopicOperation sharedSession]", + "coverage": 0 + }, + { + "name": "__43+[FIRMessagingTopicOperation sharedSession]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation initWithTopic:action:token:options:checkinService:completion:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation dealloc]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation isAsynchronous]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation isExecuting]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation setExecuting:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation isFinished]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation setFinished:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation start]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation finishWithError:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation cancel]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicOperation performSubscriptionChange]", + "coverage": 0 + }, + { + "name": "__55-[FIRMessagingTopicOperation performSubscriptionChange]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDTokenDeleteOperation.m", + "coverage": 0.12643678160919541, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenDeleteOperation initWithAuthorizedEntity:scope:checkinPreferences:keyPair:action:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenDeleteOperation performTokenOperation]", + "coverage": 0 + }, + { + "name": "__58-[FIRInstanceIDTokenDeleteOperation performTokenOperation]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenDeleteOperation handleResponseWithData:response:error:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRMessagingReceiver.m", + "coverage": 0.2012987012987013, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingReceiver initWithUserDefaults:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingReceiver didReceiveMessage:withIdentifier:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingReceiver willSendDataMessageWithID:error:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingReceiver didSendDataMessageWithID:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingReceiver didDeleteMessagesOnServer]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingReceiver scheduleIos10NotificationForMessage:withIdentifier:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingReceiver scheduleNotificationForMessage:]", + "coverage": 0 + }, + { + "name": "__55-[FIRMessagingReceiver scheduleNotificationForMessage:]_block_invoke", + "coverage": 0 + }, + { + "name": "__55-[FIRMessagingReceiver scheduleNotificationForMessage:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "+[FIRMessagingReceiver nextMessageID]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingReceiver useDirectChannel]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingReceiver setUseDirectChannel:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAppDelegate.m", + "coverage": 0.2413793103448276, + "type": "objc", + "functions": [ + { + "name": "-[FIRAppDelegate application:didFinishLaunchingWithOptions:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillResignActive:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidEnterBackground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationWillEnterForeground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidBecomeActive:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillTerminate:]", + "coverage": 0 + } + ] + }, + { + "name": "NSDictionary+FIRMessaging.m", + "coverage": 0.2857142857142857, + "type": "objc", + "functions": [ + { + "name": "-[NSDictionary(FIRMessaging) fcm_string]", + "coverage": 0 + }, + { + "name": "-[NSDictionary(FIRMessaging) fcm_hasNonStringKeysOrValues]", + "coverage": 0.4444444444444444 + }, + { + "name": "-[NSDictionary(FIRMessaging) fcm_trimNonStringValues]", + "coverage": 0.5454545454545454 + } + ] + }, + { + "name": "FIRMessagingCheckinService.m", + "coverage": 0.3058823529411765, + "type": "objc", + "functions": [ + { + "name": "FIRMessagingCheckinService_hasValidCheckinInfo", + "coverage": 0 + }, + { + "name": "FIRMessagingCheckinService_propertyNamed", + "coverage": 0.5 + }, + { + "name": "-[FIRMessagingCheckinService tryToLoadPrefetchedCheckinPreferences]", + "coverage": 0.35714285714285715 + }, + { + "name": "-[FIRMessagingCheckinService deviceAuthID]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingCheckinService secretToken]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingCheckinService versionInfo]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingCheckinService digest]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingCheckinService hasValidCheckinInfo]", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceID+Private.m", + "coverage": 0.3333333333333333, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceID(Private) cachedCheckinPreferences]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID(Private) fetchCheckinInfoWithHandler:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID(Private) appInstanceID:]", + "coverage": 1 + } + ] + }, + { + "name": "NSError+FIRInstanceID.m", + "coverage": 0.375, + "type": "objc", + "functions": [ + { + "name": "-[NSError(FIRInstanceID) instanceIDErrorCode]", + "coverage": 0 + }, + { + "name": "+[NSError(FIRInstanceID) errorWithFIRInstanceIDErrorCode:]", + "coverage": 1 + }, + { + "name": "+[NSError(FIRInstanceID) errorWithFIRInstanceIDErrorCode:userInfo:]", + "coverage": 1 + }, + { + "name": "+[NSError(FIRInstanceID) FIRInstanceIDErrorMissingCheckin]", + "coverage": 0 + } + ] + }, + { + "name": "FIRMessagingPersistentSyncMessage.m", + "coverage": 0.4, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingPersistentSyncMessage init]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPersistentSyncMessage initWithRMQID:expirationTime:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPersistentSyncMessage description]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPersistentSyncMessage debugDescription]", + "coverage": 0 + } + ] + }, + { + "name": "GPBUtilities_PackagePrivate.h", + "coverage": 0.4166666666666667, + "type": "objc", + "functions": [ + { + "name": "Definition at 54:52", + "coverage": 0 + }, + { + "name": "Definition at 65:47", + "coverage": 0 + }, + { + "name": "Definition at 73:54", + "coverage": 0 + }, + { + "name": "Definition at 79:52", + "coverage": 0 + }, + { + "name": "Definition at 85:54", + "coverage": 0 + }, + { + "name": "Definition at 91:52", + "coverage": 0 + }, + { + "name": "Definition at 97:74", + "coverage": 1 + }, + { + "name": "Definition at 101:74", + "coverage": 1 + }, + { + "name": "Definition at 109:50", + "coverage": 0 + }, + { + "name": "Definition at 117:50", + "coverage": 0 + }, + { + "name": "Definition at 125:50", + "coverage": 0 + }, + { + "name": "Definition at 134:50", + "coverage": 0 + }, + { + "name": "Definition at 143:55", + "coverage": 1 + }, + { + "name": "Definition at 155:56", + "coverage": 1 + }, + { + "name": "Definition at 165:70", + "coverage": 1 + }, + { + "name": "Definition at 169:69", + "coverage": 1 + }, + { + "name": "Definition at 173:68", + "coverage": 0 + }, + { + "name": "Definition at 178:65", + "coverage": 0 + }, + { + "name": "Definition at 192:65", + "coverage": 1 + }, + { + "name": "Definition at 197:48", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingPubSubRegistrar.m", + "coverage": 0.42424242424242425, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingPubSubRegistrar init]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSubRegistrar initWithCheckinService:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPubSubRegistrar stopAllSubscriptionRequests]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPubSubRegistrar updateSubscriptionToTopic:withToken:options:shouldDelete:handler:]", + "coverage": 0 + } + ] + }, + { + "name": "GtalkCore.pbobjc.m", + "coverage": 0.4363238512035011, + "type": "objc", + "functions": [ + { + "name": "GtalkGtalkCoreRoot_FileDescriptor", + "coverage": 1 + }, + { + "name": "+[GtalkHeartbeatPing descriptor]", + "coverage": 1 + }, + { + "name": "+[GtalkHeartbeatAck descriptor]", + "coverage": 1 + }, + { + "name": "+[GtalkErrorInfo descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkSetting descriptor]", + "coverage": 1 + }, + { + "name": "+[GtalkHeartbeatStat descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkHeartbeatConfig descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkLoginRequest descriptor]", + "coverage": 1 + }, + { + "name": "GtalkLoginRequest_AuthService_EnumDescriptor", + "coverage": 0.9130434782608695 + }, + { + "name": "GtalkLoginRequest_AuthService_IsValidValue", + "coverage": 0.9 + }, + { + "name": "+[GtalkLoginResponse descriptor]", + "coverage": 1 + }, + { + "name": "+[GtalkBindAccountRequest descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkBindAccountResponse descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkStreamErrorStanza descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkClose descriptor]", + "coverage": 1 + }, + { + "name": "+[GtalkExtension descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkMessageStanza descriptor]", + "coverage": 0 + }, + { + "name": "GtalkMessageStanza_MessageType_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkMessageStanza_MessageType_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GtalkPresenceStanza descriptor]", + "coverage": 0 + }, + { + "name": "GtalkPresenceStanza_PresenceType_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkPresenceStanza_PresenceType_IsValidValue", + "coverage": 0 + }, + { + "name": "GtalkPresenceStanza_ShowType_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkPresenceStanza_ShowType_IsValidValue", + "coverage": 0 + }, + { + "name": "GtalkPresenceStanza_ClientType_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkPresenceStanza_ClientType_IsValidValue", + "coverage": 0 + }, + { + "name": "GtalkPresenceStanza_CapabilitiesFlags_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkPresenceStanza_CapabilitiesFlags_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GtalkBatchPresenceStanza descriptor]", + "coverage": 0 + }, + { + "name": "GtalkBatchPresenceStanza_Type_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkBatchPresenceStanza_Type_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GtalkIqStanza descriptor]", + "coverage": 1 + }, + { + "name": "GtalkIqStanza_IqType_EnumDescriptor", + "coverage": 0.9130434782608695 + }, + { + "name": "GtalkIqStanza_IqType_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GtalkAppData descriptor]", + "coverage": 1 + }, + { + "name": "+[GtalkDataMessageStanza descriptor]", + "coverage": 1 + }, + { + "name": "+[GtalkTalkMetadata descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkCellTower descriptor]", + "coverage": 0 + }, + { + "name": "+[GtalkClientEvent descriptor]", + "coverage": 0 + }, + { + "name": "GtalkClientEvent_Type_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkClientEvent_Type_IsValidValue", + "coverage": 0 + }, + { + "name": "GtalkClientEvent_McsReconnectAction_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GtalkClientEvent_McsReconnectAction_IsValidValue", + "coverage": 0 + } + ] + }, + { + "name": "FIRViewController.m", + "coverage": 0.5, + "type": "objc", + "functions": [ + { + "name": "-[FIRViewController viewDidLoad]", + "coverage": 1 + }, + { + "name": "-[FIRViewController didReceiveMemoryWarning]", + "coverage": 0 + } + ] + }, + { + "name": "FIRMessagingConnection.m", + "coverage": 0.5072727272727273, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingD2SInfo initWithStreamId:d2sId:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingD2SInfo isEqual:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingD2SInfo hash]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection initWithAuthID:token:host:port:runLoop:rmq2Manager:fcmManager:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection description]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection signIn]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection setupConnectionSocket]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection connectToSocket:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection signOut]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection teardown]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection secureSocketDidConnect:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection didDisconnectWithSecureSocket:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection secureSocket:didReceiveData:withTag:]", + "coverage": 0.7368421052631579 + }, + { + "name": "-[FIRMessagingConnection secureSocket:didSendProtoWithTag:rmqId:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection sendProto:]", + "coverage": 0.5714285714285714 + }, + { + "name": "-[FIRMessagingConnection sendOnConnectOrDrop:]", + "coverage": 0 + }, + { + "name": "+[FIRMessagingConnection loginRequestWithToken:authID:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingConnection currentNetworkType]", + "coverage": 0.7894736842105263 + }, + { + "name": "-[FIRMessagingConnection sendLoginRequest:token:]", + "coverage": 0.6296296296296297 + }, + { + "name": "-[FIRMessagingConnection sendHeartbeatAck]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection sendHeartbeatPing]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingConnection createStreamAck]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection sendStreamAck]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection sendClose]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection handleIqStanza:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection didReceiveLoginResponse:]", + "coverage": 0.7083333333333334 + }, + { + "name": "-[FIRMessagingConnection didReceiveHeartbeatPing:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection didReceiveHeartbeatAck:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection didReceiveDataMessageStanza:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection didReceiveUnhandledProto:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection didReceiveStreamAck:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection didReceiveSelectiveAck:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection didReceiveClose:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection willProcessProto:]", + "coverage": 0.5945945945945946 + }, + { + "name": "-[FIRMessagingConnection willSendProto:]", + "coverage": 0.625 + }, + { + "name": "-[FIRMessagingConnection confirmAckedD2sIdsWithStreamId:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection didReceiveAckForRmqIds:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection confirmAckedS2dIdsWithStreamId:]", + "coverage": 0 + }, + { + "name": "__57-[FIRMessagingConnection confirmAckedS2dIdsWithStreamId:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection resetUnconfirmedAcks]", + "coverage": 1 + }, + { + "name": "__46-[FIRMessagingConnection resetUnconfirmedAcks]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection disconnect]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection connectionTimedOut]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection scheduleConnectionTimeoutTask]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection cancelConnectionTimeoutTask]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingConnection logMessage:messageType:isOut:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingConnection connectionTimeoutInterval]", + "coverage": 0 + } + ] + }, + { + "name": "FIRMessagingClient.m", + "coverage": 0.5215311004784688, + "type": "objc", + "functions": [ + { + "name": "FIRMessagingServerHost", + "coverage": 1 + }, + { + "name": "__FIRMessagingServerHost_block_invoke", + "coverage": 0.9 + }, + { + "name": "FIRMessagingServerPort", + "coverage": 1 + }, + { + "name": "__FIRMessagingServerPort_block_invoke", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FIRMessagingClient init]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient initWithDelegate:reachability:rmq2Manager:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingClient teardown]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingClient cancelAllRequests]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient updateSubscriptionWithToken:topic:options:shouldDelete:handler:]", + "coverage": 0 + }, + { + "name": "__85-[FIRMessagingClient updateSubscriptionWithToken:topic:options:shouldDelete:handler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient isConnected]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingClient isConnectionActive]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingClient shouldStayConnected]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient retryConnectionImmediately:]", + "coverage": 0 + }, + { + "name": "__49-[FIRMessagingClient retryConnectionImmediately:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient connectWithHandler:]", + "coverage": 0.46153846153846156 + }, + { + "name": "-[FIRMessagingClient connect]", + "coverage": 0.43478260869565216 + }, + { + "name": "-[FIRMessagingClient disconnect]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient disconnectWithTryToConnectLater:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingClient checkinFetched:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient sendMessage:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient sendOnConnectOrDrop:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient connection:didCloseForReason:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient didLoginWithConnection:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingClient connectionDidRecieveMessage:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient connectionDidReceiveAckForRmqIds:]", + "coverage": 0 + }, + { + "name": "__55-[FIRMessagingClient connectionDidReceiveAckForRmqIds:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingClient setupConnectionAndConnect]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingClient setupConnection]", + "coverage": 0.7894736842105263 + }, + { + "name": "-[FIRMessagingClient tryToConnect]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[FIRMessagingClient didConnectTimeout]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingClient scheduleConnectRetry]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingClient nextRetryInterval]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDStringEncoding.m", + "coverage": 0.5308641975308642, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDStringEncoding rfc4648Base64WebsafeStringEncoding]", + "coverage": 1 + }, + { + "name": "lcm", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStringEncoding stringEncodingWithString:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStringEncoding initWithString:]", + "coverage": 0.6896551724137931 + }, + { + "name": "-[FIRInstanceIDStringEncoding description]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDStringEncoding doPad]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDStringEncoding setDoPad:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStringEncoding setPaddingChar:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStringEncoding encode:]", + "coverage": 0.8444444444444444 + }, + { + "name": "-[FIRInstanceIDStringEncoding decode:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRMessaging.m", + "coverage": 0.5459032576505429, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingMessageInfo init]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingMessageInfo initWithStatus:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRemoteMessage init]", + "coverage": 0 + }, + { + "name": "+[FIRMessaging messaging]", + "coverage": 1 + }, + { + "name": "__25+[FIRMessaging messaging]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRMessaging extensionHelper]", + "coverage": 0 + }, + { + "name": "__31+[FIRMessaging extensionHelper]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessaging initWithAnalytics:withInstanceID:withUserDefaults:]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging dealloc]", + "coverage": 1 + }, + { + "name": "+[FIRMessaging load]", + "coverage": 1 + }, + { + "name": "+[FIRMessaging componentsToRegister]", + "coverage": 0 + }, + { + "name": "__36+[FIRMessaging componentsToRegister]_block_invoke", + "coverage": 0 + }, + { + "name": "+[FIRMessaging configureWithApp:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging configureMessaging:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging start]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging setupFileManagerSubDirectory]", + "coverage": 0.6 + }, + { + "name": "-[FIRMessaging setupNotificationListeners]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging setupReceiver]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging setupClient]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging setupDataMessageManager]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging setupRmqManager]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging setupTopics]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging setupSyncMessageManager]", + "coverage": 1 + }, + { + "name": "__39-[FIRMessaging setupSyncMessageManager]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRMessaging teardown]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging appDidReceiveMessage:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging handleContextManagerMessage:]", + "coverage": 0 + }, + { + "name": "+[FIRMessaging isAPNSSyncMessage:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging handleIncomingLinkIfNeededFromMessage:]", + "coverage": 0 + }, + { + "name": "__54-[FIRMessaging handleIncomingLinkIfNeededFromMessage:]_block_invoke", + "coverage": 0 + }, + { + "name": "__54-[FIRMessaging handleIncomingLinkIfNeededFromMessage:]_block_invoke.322", + "coverage": 0 + }, + { + "name": "-[FIRMessaging linkURLFromMessage:]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging APNSToken]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging setAPNSToken:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging setAPNSToken:type:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging isAutoInitEnabled]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging setAutoInitEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging FCMToken]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging retrieveFCMTokenForSenderID:completion:]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging deleteFCMTokenForSenderID:completion:]", + "coverage": 0.7647058823529411 + }, + { + "name": "-[FIRMessaging setDelegate:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging validateDelegateConformsToTokenAvailabilityMethods]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging notifyDelegateOfFCMTokenAvailability]", + "coverage": 0 + }, + { + "name": "__52-[FIRMessaging notifyDelegateOfFCMTokenAvailability]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessaging setUseMessagingDelegateForDirectChannel:]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging useMessagingDelegateForDirectChannel]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging applicationStateChanged]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging setShouldEstablishDirectChannel:]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging isDirectChannelEstablished]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging shouldBeConnectedAutomatically]", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRMessaging updateAutomaticClientConnection]", + "coverage": 0.2857142857142857 + }, + { + "name": "__47-[FIRMessaging updateAutomaticClientConnection]_block_invoke", + "coverage": 0 + }, + { + "name": "__47-[FIRMessaging updateAutomaticClientConnection]_block_invoke.428", + "coverage": 0 + }, + { + "name": "-[FIRMessaging notifyOfDirectChannelConnectionChange]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging connectWithCompletion:]", + "coverage": 0 + }, + { + "name": "__38-[FIRMessaging connectWithCompletion:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessaging disconnect]", + "coverage": 0 + }, + { + "name": "+[FIRMessaging normalizeTopic:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRMessaging subscribeToTopic:]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging subscribeToTopic:completion:]", + "coverage": 0.8260869565217391 + }, + { + "name": "-[FIRMessaging unsubscribeFromTopic:]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging unsubscribeFromTopic:completion:]", + "coverage": 0.8260869565217391 + }, + { + "name": "-[FIRMessaging sendMessage:to:withMessageID:timeToLive:]", + "coverage": 0 + }, + { + "name": "+[FIRMessaging createFIRMessagingMessageWithMessage:to:withID:timeToLive:delay:]", + "coverage": 1 + }, + { + "name": "+[FIRMessaging FIRMessagingSDKVersion]", + "coverage": 1 + }, + { + "name": "+[FIRMessaging FIRMessagingSDKCurrentLocale]", + "coverage": 1 + }, + { + "name": "-[FIRMessaging receiver:receivedRemoteMessage:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging reachability:statusChanged:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging onNetworkStatusChanged]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging isNetworkAvailable]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging networkType]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging didReceiveDefaultInstanceIDToken:]", + "coverage": 0 + }, + { + "name": "-[FIRMessaging defaultInstanceIDTokenWasRefreshed:]", + "coverage": 0 + }, + { + "name": "+[FIRMessaging hasSubDirectory:]", + "coverage": 0.7272727272727273 + }, + { + "name": "+[FIRMessaging pathForSubDirectory:]", + "coverage": 1 + }, + { + "name": "+[FIRMessaging createSubDirectory:]", + "coverage": 0 + }, + { + "name": "+[FIRMessaging currentLocale]", + "coverage": 1 + }, + { + "name": "+[FIRMessaging firebaseLocales]", + "coverage": 1 + }, + { + "name": "+[FIRMessaging firebaselocalesMap]", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingRemoteNotificationsProxy.m", + "coverage": 0.6026490066225165, + "type": "objc", + "functions": [ + { + "name": "+[FIRMessagingRemoteNotificationsProxy canSwizzleMethods]", + "coverage": 0 + }, + { + "name": "+[FIRMessagingRemoteNotificationsProxy swizzleMethods]", + "coverage": 0 + }, + { + "name": "+[FIRMessagingRemoteNotificationsProxy sharedProxy]", + "coverage": 0 + }, + { + "name": "__51+[FIRMessagingRemoteNotificationsProxy sharedProxy]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy init]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy swizzleMethodsIfPossible]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy unswizzleAllMethods]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy swizzleAppDelegateMethods:]", + "coverage": 0.9076923076923077 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy listenForDelegateChangesInUserNotificationCenter:]", + "coverage": 0.35714285714285715 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy swizzleUserNotificationCenterDelegate:]", + "coverage": 0.9117647058823529 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy unswizzleUserNotificationCenterDelegate:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy addDelegateObserverToUserNotificationCenter:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy removeUserNotificationCenterDelegateObserver]", + "coverage": 0.21052631578947367 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy observeValueForKeyPath:ofObject:change:context:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy saveOriginalImplementation:forSelector:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy originalImplementationForSelector:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy trackSwizzledSelector:ofClass:]", + "coverage": 0.9090909090909091 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy removeImplementationForSelector:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy swizzleSelector:inClass:withImplementation:inProtocol:]", + "coverage": 0.8648648648648649 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy unswizzleSelector:inClass:]", + "coverage": 0.88 + }, + { + "name": "-[FIRMessagingRemoteNotificationsProxy nonExistantMethodImplementationForClass:]", + "coverage": 1 + }, + { + "name": "getNamedPropertyFromObject", + "coverage": 0.7142857142857143 + }, + { + "name": "FCM_swizzle_appDidReceiveRemoteNotification", + "coverage": 1 + }, + { + "name": "FCM_swizzle_appDidReceiveRemoteNotificationWithHandler", + "coverage": 1 + }, + { + "name": "FCM_swizzle_willPresentNotificationWithHandler", + "coverage": 0.5925925925925926 + }, + { + "name": "__FCM_swizzle_willPresentNotificationWithHandler_block_invoke", + "coverage": 1 + }, + { + "name": "FCM_swizzle_didReceiveNotificationResponseWithHandler", + "coverage": 0.6101694915254238 + }, + { + "name": "__FCM_swizzle_didReceiveNotificationResponseWithHandler_block_invoke", + "coverage": 1 + }, + { + "name": "userInfoFromNotification", + "coverage": 0.6923076923076923 + }, + { + "name": "FCM_swizzle_messagingDidReceiveMessage", + "coverage": 0 + }, + { + "name": "FCM_swizzle_appDidFailToRegisterForRemoteNotifications", + "coverage": 0 + }, + { + "name": "FCM_swizzle_appDidRegisterForRemoteNotifications", + "coverage": 0 + } + ] + }, + { + "name": "FIRMessagingPacketQueue.m", + "coverage": 0.6078431372549019, + "type": "objc", + "functions": [ + { + "name": "+[FIRMessagingPacket packetWithTag:rmqId:data:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPacket init]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPacket initWithTag:rmqId:data:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPacket description]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPacketQueue init]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPacketQueue isEmpty]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPacketQueue count]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPacketQueue push:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPacketQueue pushHead:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPacketQueue pop]", + "coverage": 0.75 + } + ] + }, + { + "name": "FIRMessagingPubSub.m", + "coverage": 0.625, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingPubSub init]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub initWithClient:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPubSub subscribeWithToken:topic:options:handler:]", + "coverage": 0.7435897435897436 + }, + { + "name": "__63-[FIRMessagingPubSub subscribeWithToken:topic:options:handler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub unsubscribeWithToken:topic:options:handler:]", + "coverage": 0.6578947368421053 + }, + { + "name": "__65-[FIRMessagingPubSub unsubscribeWithToken:topic:options:handler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub subscribeToTopic:handler:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub unsubscribeFromTopic:handler:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub scheduleSync:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub pendingTopicsList:requestedUpdateForTopic:action:completion:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub pendingTopicsListDidUpdate:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub pendingTopicsListCanRequestTopicUpdates:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub archivePendingTopicsList:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPubSub restorePendingTopicsList]", + "coverage": 0.8421052631578947 + }, + { + "name": "-[FIRMessagingPubSub verifyPubSubOptions:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingPubSub addPrefixToTopic:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingPubSub removePrefixFromTopic:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingPubSub hasTopicsPrefix:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingPubSub topicRegex]", + "coverage": 1 + }, + { + "name": "__32+[FIRMessagingPubSub topicRegex]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRMessagingPubSub isValidTopicWithPrefix:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingCodedInputStream.m", + "coverage": 0.6363636363636364, + "type": "objc", + "functions": [ + { + "name": "CheckSize", + "coverage": 1 + }, + { + "name": "ReadRawByte", + "coverage": 1 + }, + { + "name": "ReadRawVarInt32", + "coverage": 0.35714285714285715 + }, + { + "name": "-[FIRMessagingCodedInputStream initWithData:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingCodedInputStream offset]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingCodedInputStream readTag:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingCodedInputStream readLength:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingCodedInputStream readDataWithLength:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingSecureSocket.m", + "coverage": 0.6518105849582173, + "type": "objc", + "functions": [ + { + "name": "LogicalRightShift32", + "coverage": 0 + }, + { + "name": "SerializedSize", + "coverage": 0.75 + }, + { + "name": "-[FIRMessagingSecureSocket init]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingSecureSocket dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingSecureSocket connectToHost:port:onRunLoop:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingSecureSocket disconnect]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingSecureSocket sendData:withTag:rmqId:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingSecureSocket stream:handleEvent:]", + "coverage": 0.7241379310344828 + }, + { + "name": "-[FIRMessagingSecureSocket openStream:isVOIPStream:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingSecureSocket closeStream:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingSecureSocket performRead]", + "coverage": 0.6826923076923077 + }, + { + "name": "-[FIRMessagingSecureSocket processCurrentInputBuffer:outOffset:]", + "coverage": 0.6551724137931034 + }, + { + "name": "-[FIRMessagingSecureSocket performWrite]", + "coverage": 0.9523809523809523 + } + ] + }, + { + "name": "FIRMessagingRmq2PersistentStore.m", + "coverage": 0.6566455696202531, + "type": "objc", + "functions": [ + { + "name": "FIRMessagingStringFromSQLiteResult", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore initWithDatabaseName:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore moveToApplicationSupportSubDirectory:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore doesFileExistInDirectory:]", + "coverage": 0 + }, + { + "name": "+[FIRMessagingRmq2PersistentStore pathForDatabase:inDirectory:]", + "coverage": 0.8 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore createTableWithName:command:]", + "coverage": 0.3333333333333333 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore dropTableWithName:]", + "coverage": 0.625 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore removeDatabase]", + "coverage": 0 + }, + { + "name": "+[FIRMessagingRmq2PersistentStore removeDatabase:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore openDatabase:]", + "coverage": 0.5625 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore updateDbWithStringRmqID]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore saveUnackedS2dMessageWithRmqId:]", + "coverage": 0.7391304347826086 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore saveMessageWithRmqId:tag:data:error:]", + "coverage": 0.5161290322580645 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore deleteMessagesFromTable:withRmqIds:]", + "coverage": 0.8522727272727273 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore queryHighestRmqId]", + "coverage": 0.7894736842105263 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore queryLastRmqId]", + "coverage": 0.7894736842105263 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore updateLastOutgoingRmqId:]", + "coverage": 0.6190476190476191 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore unackedS2dRmqIds]", + "coverage": 0.8 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore scanOutgoingRmqMessagesWithHandler:]", + "coverage": 0.8666666666666667 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore querySyncMessageWithRmqID:]", + "coverage": 0.9111111111111111 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore deleteSyncMessageWithRmqID:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore deleteExpiredOrFinishedSyncMessages:]", + "coverage": 0.6571428571428571 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore saveSyncMessageWithRmqID:expirationTime:apnsReceived:mcsReceived:error:]", + "coverage": 0.6363636363636364 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore updateSyncMessageViaAPNSWithRmqID:error:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore updateSyncMessageViaMCSWithRmqID:error:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore updateSyncMessageWithRmqID:column:value:error:]", + "coverage": 0.7058823529411765 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore lastErrorMessage]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore lastErrorCode]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore logError]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingRmq2PersistentStore logErrorAndFinalizeStatement:]", + "coverage": 0 + } + ] + }, + { + "name": "NSError+FIRMessaging.m", + "coverage": 0.6666666666666666, + "type": "objc", + "functions": [ + { + "name": "-[NSError(FIRMessaging) fcmErrorCode]", + "coverage": 0 + }, + { + "name": "+[NSError(FIRMessaging) errorWithFCMErrorCode:]", + "coverage": 1 + }, + { + "name": "+[NSError(FIRMessaging) fcm_errorWithCode:userInfo:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDTokenManager.m", + "coverage": 0.7067039106145251, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenManager init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager configureTokenOperations]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke.52", + "coverage": 1 + }, + { + "name": "__93-[FIRInstanceIDTokenManager fetchNewTokenWithAuthorizedEntity:scope:keyPair:options:handler:]_block_invoke_2.53", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager cachedTokenInfoWithAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager deleteTokenWithAuthorizedEntity:scope:keyPair:handler:]", + "coverage": 0.9166666666666666 + }, + { + "name": "__83-[FIRInstanceIDTokenManager deleteTokenWithAuthorizedEntity:scope:keyPair:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__83-[FIRInstanceIDTokenManager deleteTokenWithAuthorizedEntity:scope:keyPair:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]", + "coverage": 0 + }, + { + "name": "__64-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]_block_invoke", + "coverage": 0 + }, + { + "name": "__64-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]_block_invoke.86", + "coverage": 0 + }, + { + "name": "__64-[FIRInstanceIDTokenManager deleteAllTokensWithKeyPair:handler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager deleteAllTokensLocallyWithHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager stopAllTokenOperations]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenManager store:didDeleteFCMScopedTokensForCheckin:]", + "coverage": 0 + }, + { + "name": "__70-[FIRInstanceIDTokenManager store:didDeleteFCMScopedTokensForCheckin:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager createFetchOperationWithAuthorizedEntity:scope:options:keyPair:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager createDeleteOperationWithAuthorizedEntity:scope:checkinPreferences:keyPair:action:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenManager checkForTokenRefreshPolicy]", + "coverage": 0.8125 + }, + { + "name": "-[FIRInstanceIDTokenManager updateTokensToAPNSDeviceToken:isSandbox:]", + "coverage": 0.9333333333333333 + } + ] + }, + { + "name": "FIRInstanceIDLogger.m", + "coverage": 0.72, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDLogger formatMessageCode:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDLogger logFuncDebug:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDLogger logFuncInfo:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDLogger logFuncNotice:messageCode:msg:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDLogger logFuncWarning:messageCode:msg:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDLogger logFuncError:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "FIRInstanceIDSharedLogger", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDSharedLogger_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingDelayedMessageQueue.m", + "coverage": 0.723404255319149, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingDelayedMessageQueue init]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingDelayedMessageQueue initWithRmqScanner:sendDelayedMessagesHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDelayedMessageQueue queueMessage:]", + "coverage": 0.75 + }, + { + "name": "-[FIRMessagingDelayedMessageQueue removeDelayedMessages]", + "coverage": 0.5862068965517241 + }, + { + "name": "__56-[FIRMessagingDelayedMessageQueue removeDelayedMessages]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRMessagingDelayedMessageQueue sendMessages]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDelayedMessageQueue messageCount]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDelayedMessageQueue isTimeoutScheduled]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDelayedMessageQueue calculateTimeoutInMillisWithDelayInSeconds:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDelayedMessageQueue scheduleTimeoutInMillis:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDelayedMessageQueue cancelTimeout]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceID.m", + "coverage": 0.7266414141414141, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDResult copyWithZone:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID instanceID]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIRInstanceID initPrivately]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID instanceIDForTests]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID token]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID instanceIDWithHandler:]", + "coverage": 1 + }, + { + "name": "__39-[FIRInstanceID instanceIDWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__39-[FIRInstanceID instanceIDWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID cachedTokenIfAvailable]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID setDefaultFCMToken:]", + "coverage": 0.8666666666666667 + }, + { + "name": "-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]", + "coverage": 0.8584905660377359 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke.163", + "coverage": 0.9302325581395349 + }, + { + "name": "__65-[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler:]_block_invoke_2.171", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]", + "coverage": 0.84375 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke", + "coverage": 0.8 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke.195", + "coverage": 0.8695652173913043 + }, + { + "name": "__63-[FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:]_block_invoke_2.196", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID asyncLoadKeyPairWithHandler:]", + "coverage": 1 + }, + { + "name": "__45-[FIRInstanceID asyncLoadKeyPairWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__45-[FIRInstanceID asyncLoadKeyPairWithHandler:]_block_invoke_2", + "coverage": 0.7222222222222222 + }, + { + "name": "-[FIRInstanceID getIDWithHandler:]", + "coverage": 0.7837837837837838 + }, + { + "name": "__34-[FIRInstanceID getIDWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__34-[FIRInstanceID getIDWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__34-[FIRInstanceID getIDWithHandler:]_block_invoke.223", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID deleteIDWithHandler:]", + "coverage": 0.8333333333333334 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke", + "coverage": 0.5555555555555556 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke.240", + "coverage": 0 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke_2.241", + "coverage": 0 + }, + { + "name": "__37-[FIRInstanceID deleteIDWithHandler:]_block_invoke.250", + "coverage": 0.8 + }, + { + "name": "-[FIRInstanceID notifyIdentityReset]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID deleteIdentityWithHandler:]", + "coverage": 1 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke", + "coverage": 0.9137931034482759 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke_3", + "coverage": 0 + }, + { + "name": "__43-[FIRInstanceID deleteIdentityWithHandler:]_block_invoke_4", + "coverage": 0 + }, + { + "name": "+[FIRInstanceID load]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID componentsToRegister]", + "coverage": 1 + }, + { + "name": "__37+[FIRInstanceID componentsToRegister]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID configureWithApp:]", + "coverage": 0.4444444444444444 + }, + { + "name": "-[FIRInstanceID configureInstanceIDWithOptions:app:]", + "coverage": 0.7222222222222222 + }, + { + "name": "+[FIRInstanceID configureErrorWithReason:]", + "coverage": 0 + }, + { + "name": "+[FIRInstanceID exitWithReason:forFirebaseApp:]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID didCompleteConfigure]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID isFCMAutoInitEnabled]", + "coverage": 0.8709677419354839 + }, + { + "name": "-[FIRInstanceID start]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FIRInstanceID setupTokenManager]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID setupKeyPairManager]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID setupNotificationListeners]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID maxRetryCountForDefaultToken]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceID minIntervalForDefaultTokenRetry]", + "coverage": 0 + }, + { + "name": "+[FIRInstanceID maxRetryIntervalForDefaultTokenInSeconds]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID retryIntervalToFetchDefaultToken]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIRInstanceID fetchDefaultToken]", + "coverage": 1 + }, + { + "name": "__34-[FIRInstanceID fetchDefaultToken]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID defaultTokenWithHandler:]", + "coverage": 0.89 + }, + { + "name": "__41-[FIRInstanceID defaultTokenWithHandler:]_block_invoke", + "coverage": 0.8133333333333334 + }, + { + "name": "__41-[FIRInstanceID defaultTokenWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__41-[FIRInstanceID defaultTokenWithHandler:]_block_invoke.406", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID notifyAPNSTokenIsSet:]", + "coverage": 0 + }, + { + "name": "__38-[FIRInstanceID notifyAPNSTokenIsSet:]_block_invoke", + "coverage": 0 + }, + { + "name": "__38-[FIRInstanceID notifyAPNSTokenIsSet:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "-[FIRInstanceID isSandboxApp]", + "coverage": 1 + }, + { + "name": "__29-[FIRInstanceID isSandboxApp]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceID isProductionApp]", + "coverage": 0.22556390977443608 + }, + { + "name": "-[FIRInstanceID logAPNSConfigurationError:]", + "coverage": 0.875 + } + ] + }, + { + "name": "FIRMessagingPendingTopicsList.m", + "coverage": 0.7335766423357665, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingTopicBatch initWithAction:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingTopicBatch encodeWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingTopicBatch initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPendingTopicsList init]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingPendingTopicsList pruneTopicBatches:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPendingTopicsList encodeWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPendingTopicsList initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingPendingTopicsList numberOfBatches]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPendingTopicsList addOperationForTopic:withAction:completion:]", + "coverage": 0.8205128205128205 + }, + { + "name": "__76-[FIRMessagingPendingTopicsList addOperationForTopic:withAction:completion:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPendingTopicsList resumeOperationsIfNeeded]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[FIRMessagingPendingTopicsList subscriptionErrorIsRecoverable:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingPendingTopicsList beginUpdateForCurrentBatchTopic:]", + "coverage": 1 + }, + { + "name": "__65-[FIRMessagingPendingTopicsList beginUpdateForCurrentBatchTopic:]_block_invoke", + "coverage": 1 + }, + { + "name": "__65-[FIRMessagingPendingTopicsList beginUpdateForCurrentBatchTopic:]_block_invoke_2", + "coverage": 0.8055555555555556 + }, + { + "name": "__65-[FIRMessagingPendingTopicsList beginUpdateForCurrentBatchTopic:]_block_invoke_3", + "coverage": 0 + }, + { + "name": "__65-[FIRMessagingPendingTopicsList beginUpdateForCurrentBatchTopic:]_block_invoke.130", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingContextManagerService.m", + "coverage": 0.7361111111111112, + "type": "objc", + "functions": [ + { + "name": "+[FIRMessagingContextManagerService isContextManagerMessage:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingContextManagerService handleContextManagerMessage:]", + "coverage": 0.7272727272727273 + }, + { + "name": "+[FIRMessagingContextManagerService handleContextManagerLocalTimeMessage:]", + "coverage": 0.7307692307692307 + }, + { + "name": "+[FIRMessagingContextManagerService scheduleLocalNotificationForMessage:atDate:]", + "coverage": 0.6428571428571429 + }, + { + "name": "+[FIRMessagingContextManagerService parseDataFromMessage:]", + "coverage": 0.9333333333333333 + } + ] + }, + { + "name": "FIRInstanceIDTokenFetchOperation.m", + "coverage": 0.75, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenFetchOperation initWithAuthorizedEntity:scope:options:checkinPreferences:keyPair:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenFetchOperation performTokenOperation]", + "coverage": 0.9206349206349206 + }, + { + "name": "__57-[FIRInstanceIDTokenFetchOperation performTokenOperation]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenFetchOperation handleResponseWithData:response:error:]", + "coverage": 0.5454545454545454 + }, + { + "name": "__74-[FIRInstanceIDTokenFetchOperation handleResponseWithData:response:error:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenFetchOperation parseFetchTokenResponse:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingUtilities.m", + "coverage": 0.7535211267605634, + "type": "objc", + "functions": [ + { + "name": "FIRMessagingGetTagForProto", + "coverage": 0.7777777777777778 + }, + { + "name": "FIRMessagingGetClassForTag", + "coverage": 0.8571428571428571 + }, + { + "name": "FIRMessagingGetRmq2Id", + "coverage": 0.75 + }, + { + "name": "FIRMessagingSetRmq2Id", + "coverage": 0.8571428571428571 + }, + { + "name": "FIRMessagingGetLastStreamId", + "coverage": 0.5 + }, + { + "name": "FIRMessagingSetLastStreamId", + "coverage": 0.9090909090909091 + }, + { + "name": "FIRMessagingCurrentTimestampInSeconds", + "coverage": 1 + }, + { + "name": "FIRMessagingCurrentTimestampInMilliseconds", + "coverage": 1 + }, + { + "name": "FIRMessagingCurrentAppVersion", + "coverage": 0 + }, + { + "name": "FIRMessagingAppIdentifier", + "coverage": 1 + }, + { + "name": "FIRMessagingGetFreeDiskSpaceInMB", + "coverage": 0.8 + }, + { + "name": "FIRMessagingUIApplication", + "coverage": 1 + }, + { + "name": "FIRMessagingSupportedDirectory", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingVersionUtilities.m", + "coverage": 0.7567567567567568, + "type": "objc", + "functions": [ + { + "name": "FIRMessagingParseCurrentLibraryVersion", + "coverage": 1 + }, + { + "name": "__FIRMessagingParseCurrentLibraryVersion_block_invoke", + "coverage": 0.92 + }, + { + "name": "FIRMessagingCurrentLibraryVersion", + "coverage": 1 + }, + { + "name": "FIRMessagingCurrentLibraryVersionMajor", + "coverage": 0 + }, + { + "name": "FIRMessagingCurrentLibraryVersionMinor", + "coverage": 0 + }, + { + "name": "FIRMessagingCurrentLibraryVersionPatch", + "coverage": 0 + }, + { + "name": "FIRMessagingCurrentLibraryVersionIsBeta", + "coverage": 0 + } + ] + }, + { + "name": "FIRMessagingSyncMessageManager.m", + "coverage": 0.77, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingSyncMessageManager init]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingSyncMessageManager initWithRmqManager:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingSyncMessageManager removeExpiredSyncMessages]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIRMessagingSyncMessageManager didReceiveAPNSSyncMessage:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingSyncMessageManager didReceiveMCSSyncMessage:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingSyncMessageManager didReceiveSyncMessage:viaAPNS:viaMCS:]", + "coverage": 0.71875 + }, + { + "name": "+[FIRMessagingSyncMessageManager expirationTimeForSyncMessage:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDVersionUtilities.m", + "coverage": 0.775, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDParseCurrentLibraryVersion", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDParseCurrentLibraryVersion_block_invoke", + "coverage": 0.9285714285714286 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersion", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionMajor", + "coverage": 0 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionMinor", + "coverage": 0 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionPatch", + "coverage": 0 + }, + { + "name": "FIRInstanceIDCurrentLibraryVersionIsBeta", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDURLQueryItem.m", + "coverage": 0.78125, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDURLQueryItem queryItemWithName:value:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDURLQueryItem initWithName:value:]", + "coverage": 1 + }, + { + "name": "FIRInstanceIDQueryFromQueryItems", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "FIRInstanceIDBackupExcludedPlist.m", + "coverage": 0.7816901408450704, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDBackupExcludedPlist initWithFileName:subDirectory:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist writeDictionary:error:]", + "coverage": 0.696969696969697 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist deleteFile:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist contentAsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist moveToApplicationSupportSubDirectory:]", + "coverage": 0.5428571428571428 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist doesFileExist]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist plistDirectory]", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist plistPathInDirectory:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist pathWithName:inDirectory:]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist doesFileExistInDirectory:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDBackupExcludedPlist supportedDirectory]", + "coverage": 1 + } + ] + }, + { + "name": "FIRDependency.m", + "coverage": 0.7857142857142857, + "type": "objc", + "functions": [ + { + "name": "+[FIRDependency dependencyWithProtocol:]", + "coverage": 0 + }, + { + "name": "+[FIRDependency dependencyWithProtocol:isRequired:]", + "coverage": 1 + }, + { + "name": "-[FIRDependency initWithProtocol:isRequired:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRBundleUtil.m", + "coverage": 0.7872340425531915, + "type": "objc", + "functions": [ + { + "name": "+[FIRBundleUtil relevantBundles]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil optionsDictionaryPathWithResourceName:andFileType:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil relevantURLSchemes]", + "coverage": 0 + }, + { + "name": "+[FIRBundleUtil hasBundleIdentifierPrefix:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil bundleIdentifierByRemovingLastPartFrom:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinStore.m", + "coverage": 0.7874396135265701, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDCheckinStore initWithCheckinPlistFileName:subDirectoryName:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore initWithCheckinPlist:keychain:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore hasCheckinPlist]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore bundleIdentifierForKeychainAccount]", + "coverage": 1 + }, + { + "name": "__63-[FIRInstanceIDCheckinStore bundleIdentifierForKeychainAccount]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore saveCheckinPreferences:handler:]", + "coverage": 0.5573770491803278 + }, + { + "name": "__60-[FIRInstanceIDCheckinStore saveCheckinPreferences:handler:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinStore removeCheckinPreferencesWithHandler:]", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceIDCheckinStore removeCheckinPreferencesWithHandler:]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRInstanceIDCheckinStore cachedCheckinPreferences]", + "coverage": 0.7073170731707317 + }, + { + "name": "-[FIRInstanceIDCheckinStore migrateCheckinItemIfNeeded]", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingExtensionHelper.m", + "coverage": 0.8053097345132744, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingExtensionHelper populateNotificationContent:withContentHandler:]", + "coverage": 0.8636363636363636 + }, + { + "name": "__78-[FIRMessagingExtensionHelper populateNotificationContent:withContentHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRMessagingExtensionHelper loadAttachmentForURL:completionHandler:]", + "coverage": 1 + }, + { + "name": "__70-[FIRMessagingExtensionHelper loadAttachmentForURL:completionHandler:]_block_invoke", + "coverage": 0.4864864864864865 + }, + { + "name": "-[FIRMessagingExtensionHelper deliverNotification]", + "coverage": 1 + } + ] + }, + { + "name": "FIRConfiguration.m", + "coverage": 0.8076923076923077, + "type": "objc", + "functions": [ + { + "name": "+[FIRConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__34+[FIRConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration init]", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration setLoggerLevel:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRMessagingDataMessageManager.m", + "coverage": 0.8086124401913876, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingDataMessageManager initWithDelegate:client:rmq2Manager:syncMessageManager:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDataMessageManager setDeviceAuthID:secretToken:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDataMessageManager refreshDelayedMessages]", + "coverage": 1 + }, + { + "name": "__56-[FIRMessagingDataMessageManager refreshDelayedMessages]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDataMessageManager processPacket:]", + "coverage": 0.6428571428571429 + }, + { + "name": "-[FIRMessagingDataMessageManager handleMCSDataMessage:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingDataMessageManager parseDataMessage:]", + "coverage": 0.746268656716418 + }, + { + "name": "-[FIRMessagingDataMessageManager didReceiveParsedMessage:]", + "coverage": 0.5909090909090909 + }, + { + "name": "-[FIRMessagingDataMessageManager filterInternalFIRMessagingKeysFromMessage:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDataMessageManager sendDataMessageStanza:]", + "coverage": 0.91 + }, + { + "name": "-[FIRMessagingDataMessageManager sendDelayedMessages:]", + "coverage": 0.375 + }, + { + "name": "-[FIRMessagingDataMessageManager didSendDataMessageStanza:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingDataMessageManager addParamWithKey:value:toStanza:]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[FIRMessagingDataMessageManager addData:toStanza:]", + "coverage": 0.5 + }, + { + "name": "-[FIRMessagingDataMessageManager willSendDataMessageSuccess:withMessageId:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDataMessageManager willSendDataMessageFail:withMessageId:error:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDataMessageManager resendMessagesWithConnection:]", + "coverage": 1 + }, + { + "name": "__63-[FIRMessagingDataMessageManager resendMessagesWithConnection:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDataMessageManager handleExpirationForDataMessage:]", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRMessagingDataMessageManager delayForMessage:]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[FIRMessagingDataMessageManager delayMessage:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingDataMessageManager tryToSendDataMessageStanza:]", + "coverage": 0.625 + }, + { + "name": "-[FIRMessagingDataMessageManager categoryForUpstreamMessages]", + "coverage": 1 + } + ] + }, + { + "name": "FIRLogger.m", + "coverage": 0.8118811881188119, + "type": "objc", + "functions": [ + { + "name": "FIRLoggerInitializeASL", + "coverage": 1 + }, + { + "name": "__FIRLoggerInitializeASL_block_invoke", + "coverage": 0.9333333333333333 + }, + { + "name": "FIRSetAnalyticsDebugMode", + "coverage": 0 + }, + { + "name": "FIRSetLoggerLevel", + "coverage": 0 + }, + { + "name": "FIRResetLogger", + "coverage": 1 + }, + { + "name": "FIRSetLoggerUserDefaults", + "coverage": 1 + }, + { + "name": "FIRIsLoggableLevel", + "coverage": 0 + }, + { + "name": "FIRLogBasic", + "coverage": 1 + }, + { + "name": "Definition at 158:95", + "coverage": 1 + }, + { + "name": "+[FIRLoggerWrapper logWithLevel:withService:withCode:withMessage:withArgs:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRInstanceIDTokenOperation.m", + "coverage": 0.8323353293413174, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDTokenOperation sharedURLSession]", + "coverage": 0 + }, + { + "name": "__47+[FIRInstanceIDTokenOperation sharedURLSession]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenOperation initWithAction:forAuthorizedEntity:scope:options:checkinPreferences:keyPair:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation addCompletionHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation isAsynchronous]", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDTokenOperation isExecuting]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation setExecuting:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation isFinished]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation setFinished:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation start]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FIRInstanceIDTokenOperation finishWithResult:token:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation cancel]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation performTokenOperation]", + "coverage": 0 + }, + { + "name": "+[FIRInstanceIDTokenOperation requestWithAuthHeader:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenOperation standardQueryItemsWithDeviceID:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenOperation queryItemsWithKeyPair:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDKeyPairStore.m", + "coverage": 0.8351449275362319, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDKeyDataWithTag", + "coverage": 0.8333333333333334 + }, + { + "name": "FIRInstanceIDCachedKeyRefWithTag", + "coverage": 0.7777777777777778 + }, + { + "name": "FIRInstanceIDHasMigratedKeyPair", + "coverage": 1 + }, + { + "name": "FIRInstanceIDLegacyPublicTagWithSubtype", + "coverage": 1 + }, + { + "name": "FIRInstanceIDLegacyPrivateTagWithSubtype", + "coverage": 1 + }, + { + "name": "FIRInstanceIDPublicTagWithSubtype", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDPublicTagWithSubtype_block_invoke", + "coverage": 1 + }, + { + "name": "FIRInstanceIDPrivateTagWithSubtype", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDPrivateTagWithSubtype_block_invoke", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCreationTimeKeyWithSubtype", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore invalidateKeyPairsIfNeeded]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore hasCachedKeyPairs]", + "coverage": 0.29411764705882354 + }, + { + "name": "-[FIRInstanceIDKeyPairStore appIdentityWithError:]", + "coverage": 0.6 + }, + { + "name": "-[FIRInstanceIDKeyPairStore loadKeyPairWithError:]", + "coverage": 0.78125 + }, + { + "name": "-[FIRInstanceIDKeyPairStore generateAndSaveKeyWithSubtype:creationTime:error:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRInstanceIDKeyPairStore validCachedKeyPairWithSubtype:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore cachedKeyPairWithSubtype:error:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDKeyPairStore keyPairForPrivateKeyTag:publicKeyTag:error:]", + "coverage": 0.8387096774193549 + }, + { + "name": "-[FIRInstanceIDKeyPairStore migrateKeyPairCacheIfNeededWithHandler:]", + "coverage": 0.8771929824561403 + }, + { + "name": "__68-[FIRInstanceIDKeyPairStore migrateKeyPairCacheIfNeededWithHandler:]_block_invoke", + "coverage": 0.8636363636363636 + }, + { + "name": "__68-[FIRInstanceIDKeyPairStore migrateKeyPairCacheIfNeededWithHandler:]_block_invoke_2", + "coverage": 0.6428571428571429 + }, + { + "name": "-[FIRInstanceIDKeyPairStore updateKeyRef:withTag:handler:]", + "coverage": 1 + }, + { + "name": "__58-[FIRInstanceIDKeyPairStore updateKeyRef:withTag:handler:]_block_invoke", + "coverage": 0.7619047619047619 + }, + { + "name": "__58-[FIRInstanceIDKeyPairStore updateKeyRef:withTag:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPairStore deleteSavedKeyPairWithSubtype:handler:]", + "coverage": 0.9375 + }, + { + "name": "__67-[FIRInstanceIDKeyPairStore deleteSavedKeyPairWithSubtype:handler:]_block_invoke", + "coverage": 0.76 + }, + { + "name": "+[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:publicTag:handler:]", + "coverage": 1 + }, + { + "name": "__75+[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:publicTag:handler:]_block_invoke", + "coverage": 0.7619047619047619 + }, + { + "name": "__75+[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:publicTag:handler:]_block_invoke_2", + "coverage": 0.5454545454545454 + }, + { + "name": "-[FIRInstanceIDKeyPairStore removeKeyPairCreationTimePlistWithError:]", + "coverage": 0.5 + }, + { + "name": "+[FIRInstanceIDKeyPairStore keyStoreFileName]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDUtilities.m", + "coverage": 0.8547008547008547, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDRegisterServer", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentTimestampInSeconds", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentTimestampInMilliseconds", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentAppVersion", + "coverage": 1 + }, + { + "name": "FIRInstanceIDAppIdentifier", + "coverage": 1 + }, + { + "name": "FIRInstanceIDFirebaseAppID", + "coverage": 1 + }, + { + "name": "FIRInstanceIDDeviceModel", + "coverage": 1 + }, + { + "name": "__FIRInstanceIDDeviceModel_block_invoke", + "coverage": 1 + }, + { + "name": "FIRInstanceIDOperatingSystemVersion", + "coverage": 1 + }, + { + "name": "FIRInstanceIDHasLocaleChanged", + "coverage": 1 + }, + { + "name": "FIRInstanceIDIsValidGCMScope", + "coverage": 1 + }, + { + "name": "FIRInstanceIDStringForAPNSDeviceToken", + "coverage": 1 + }, + { + "name": "FIRInstanceIDAPNSTupleStringForTokenAndServerType", + "coverage": 1 + }, + { + "name": "FIRInstanceIDCurrentGCMVersion", + "coverage": 0.6 + }, + { + "name": "FIRInstanceIDCurrentLocale", + "coverage": 0.4583333333333333 + } + ] + }, + { + "name": "FIRInstanceIDStore.m", + "coverage": 0.8579234972677595, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDStore initWithDelegate:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore initWithCheckinStore:tokenStore:delegate:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStore hasSubDirectory:]", + "coverage": 0.8181818181818182 + }, + { + "name": "+[FIRInstanceIDStore supportedDirectory]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStore pathForSupportSubDirectory:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDStore createSubDirectory:]", + "coverage": 0.6 + }, + { + "name": "+[FIRInstanceIDStore removeSubDirectory:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore resetCredentialsIfNeeded]", + "coverage": 1 + }, + { + "name": "__46-[FIRInstanceIDStore resetCredentialsIfNeeded]_block_invoke", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRInstanceIDStore tokenInfoWithAuthorizedEntity:scope:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRInstanceIDStore cachedTokenInfos]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore saveTokenInfo:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore removeCachedTokenWithAuthorizedEntity:scope:]", + "coverage": 0.4444444444444444 + }, + { + "name": "-[FIRInstanceIDStore removeAllCachedTokensWithHandler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore saveCheckinPreferences:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore cachedCheckinPreferences]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDStore removeCheckinPreferencesWithHandler:]", + "coverage": 1 + }, + { + "name": "__58-[FIRInstanceIDStore removeCheckinPreferencesWithHandler:]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingLogger.m", + "coverage": 0.8679245283018868, + "type": "objc", + "functions": [ + { + "name": "+[FIRMessagingLogger standardLogger]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingLogger formatMessageCode:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingLogger logFuncDebug:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingLogger logFuncInfo:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingLogger logFuncNotice:messageCode:msg:]", + "coverage": 0 + }, + { + "name": "-[FIRMessagingLogger logFuncWarning:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingLogger logFuncError:messageCode:msg:]", + "coverage": 1 + }, + { + "name": "FIRMessagingSharedLogger", + "coverage": 1 + }, + { + "name": "__FIRMessagingSharedLogger_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingAnalytics.m", + "coverage": 0.8758169934640523, + "type": "objc", + "functions": [ + { + "name": "+[FIRMessagingAnalytics canLogNotification:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingAnalytics logOpenNotification:toAnalytics:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingAnalytics logForegroundNotification:toAnalytics:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingAnalytics logEvent:withNotification:toAnalytics:]", + "coverage": 0.6666666666666666 + }, + { + "name": "+[FIRMessagingAnalytics paramsForEvent:withNotification:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingAnalytics logUserPropertyForConversionTracking:toAnalytics:]", + "coverage": 0.8666666666666667 + }, + { + "name": "+[FIRMessagingAnalytics logMessage:toAnalytics:]", + "coverage": 0.6071428571428571 + }, + { + "name": "+[FIRMessagingAnalytics currentUIApplication]", + "coverage": 1 + } + ] + }, + { + "name": "FIRApp.m", + "coverage": 0.8848, + "type": "objc", + "functions": [ + { + "name": "+[FIRApp configure]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithOptions:]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithName:options:]", + "coverage": 0.9333333333333333 + }, + { + "name": "+[FIRApp defaultApp]", + "coverage": 1 + }, + { + "name": "+[FIRApp appNamed:]", + "coverage": 1 + }, + { + "name": "+[FIRApp allApps]", + "coverage": 1 + }, + { + "name": "+[FIRApp resetApps]", + "coverage": 1 + }, + { + "name": "-[FIRApp deleteApp:]", + "coverage": 0.7916666666666666 + }, + { + "name": "+[FIRApp addAppToAppDictionary:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRApp initInstanceWithName:options:]", + "coverage": 1 + }, + { + "name": "-[FIRApp configureCore]", + "coverage": 0.7962962962962963 + }, + { + "name": "-[FIRApp options]", + "coverage": 1 + }, + { + "name": "-[FIRApp setDataCollectionDefaultEnabled:]", + "coverage": 0.9354838709677419 + }, + { + "name": "-[FIRApp isDataCollectionDefaultEnabled]", + "coverage": 1 + }, + { + "name": "+[FIRApp sendNotificationsToSDKs:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForMissingOptions]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForSubspecConfigurationFailureWithDomain:errorCode:service:reason:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForInvalidAppID]", + "coverage": 0 + }, + { + "name": "+[FIRApp isDefaultAppConfigured]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerLibrary:withVersion:]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerInternalLibrary:withName:withVersion:]", + "coverage": 1 + }, + { + "name": "__55+[FIRApp registerInternalLibrary:withName:withVersion:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRApp firebaseUserAgent]", + "coverage": 1 + }, + { + "name": "-[FIRApp checkExpectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp isAppIDValid]", + "coverage": 1 + }, + { + "name": "+[FIRApp validateAppID:]", + "coverage": 0.9473684210526315 + }, + { + "name": "+[FIRApp actualBundleID]", + "coverage": 0 + }, + { + "name": "+[FIRApp validateAppIDFormat:withVersion:]", + "coverage": 0.8450704225352113 + }, + { + "name": "+[FIRApp validateAppIDFingerprint:withVersion:]", + "coverage": 0.8620689655172413 + }, + { + "name": "-[FIRApp expectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp clearDataCollectionSwitchFromUserDefaults]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromUserDefaultsForApp:]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromPlist]", + "coverage": 1 + }, + { + "name": "__43+[FIRApp readDataCollectionSwitchFromPlist]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRApp sendLogsWithServiceName:version:error:]", + "coverage": 0.2631578947368421 + } + ] + }, + { + "name": "FIRInstanceIDTokenInfo.m", + "coverage": 0.889763779527559, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDTokenInfo initWithAuthorizedEntity:scope:token:appVersion:firebaseAppID:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenInfo isFresh]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenInfo initWithCoder:]", + "coverage": 0.78125 + }, + { + "name": "-[FIRInstanceIDTokenInfo encodeWithCoder:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinPreferences.m", + "coverage": 0.8913043478260869, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDCheckinPreferences checkinPlistContents]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences hasCheckinInfo]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences hasValidCheckinInfo]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences setHasPreCachedAuthCredentials:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinService.m", + "coverage": 0.896414342629482, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDCheckinService init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinService dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinService checkinWithExistingCheckin:completion:]", + "coverage": 0.9649122807017544 + }, + { + "name": "__69-[FIRInstanceIDCheckinService checkinWithExistingCheckin:completion:]_block_invoke", + "coverage": 0.7105263157894737 + }, + { + "name": "-[FIRInstanceIDCheckinService stopFetching]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinService checkinParametersWithExistingCheckin:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDCheckinService setCheckinTestBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDAPNSInfo.m", + "coverage": 0.9090909090909091, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDAPNSInfo initWithDeviceToken:isSandbox:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAPNSInfo initWithTokenOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAPNSInfo initWithCoder:]", + "coverage": 0.75 + }, + { + "name": "-[FIRInstanceIDAPNSInfo encodeWithCoder:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAPNSInfo isEqualToAPNSInfo:]", + "coverage": 0.7142857142857143 + } + ] + }, + { + "name": "FIRInstanceIDAuthService.m", + "coverage": 0.9166666666666666, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDAuthService initWithCheckinService:store:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService initWithStore:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService scheduleCheckin:]", + "coverage": 0.7 + }, + { + "name": "-[FIRInstanceIDAuthService startCheckinTimerWithDuration:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService clearScheduledCheckinTimer]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService onScheduledCheckinTimerFired:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService performScheduledCheckin]", + "coverage": 1 + }, + { + "name": "__51-[FIRInstanceIDAuthService performScheduledCheckin]_block_invoke", + "coverage": 1 + }, + { + "name": "__51-[FIRInstanceIDAuthService performScheduledCheckin]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService calculateNextCheckinRetryIntervalInSeconds]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[FIRInstanceIDAuthService hasValidCheckinInfo]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]", + "coverage": 1 + }, + { + "name": "__56-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "__56-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__56-[FIRInstanceIDAuthService fetchCheckinInfoWithHandler:]_block_invoke_3", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService checkinPreferences]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService stopCheckinRequest]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService resetCheckinWithHandler:]", + "coverage": 0 + }, + { + "name": "__52-[FIRInstanceIDAuthService resetCheckinWithHandler:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRInstanceIDAuthService notifyCheckinHandlersWithCheckin:error:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthService cachedCheckinMatchesCheckin:]", + "coverage": 0.5714285714285714 + } + ] + }, + { + "name": "FIRInstanceIDKeychain.m", + "coverage": 0.9227053140096618, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDKeychain sharedInstance]", + "coverage": 1 + }, + { + "name": "__39+[FIRInstanceIDKeychain sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain init]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain itemWithQuery:]", + "coverage": 1 + }, + { + "name": "__39-[FIRInstanceIDKeychain itemWithQuery:]_block_invoke", + "coverage": 0.8571428571428571 + }, + { + "name": "-[FIRInstanceIDKeychain removeItemWithQuery:handler:]", + "coverage": 1 + }, + { + "name": "__53-[FIRInstanceIDKeychain removeItemWithQuery:handler:]_block_invoke", + "coverage": 0.8260869565217391 + }, + { + "name": "__53-[FIRInstanceIDKeychain removeItemWithQuery:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain addItemWithQuery:handler:]", + "coverage": 1 + }, + { + "name": "__50-[FIRInstanceIDKeychain addItemWithQuery:handler:]_block_invoke", + "coverage": 0.6470588235294118 + }, + { + "name": "__50-[FIRInstanceIDKeychain addItemWithQuery:handler:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeychain generateKeyPairWithPrivateTag:publicTag:]", + "coverage": 1 + }, + { + "name": "__65-[FIRInstanceIDKeychain generateKeyPairWithPrivateTag:publicTag:]_block_invoke", + "coverage": 0.6666666666666666 + } + ] + }, + { + "name": "FIRInstanceIDAuthKeyChain.m", + "coverage": 0.9405405405405406, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDAuthKeychain initWithIdentifier:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDAuthKeychain keychainQueryForService:account:generic:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain keychainQueryForService:account:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain itemsMatchingService:account:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain dataForService:account:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain removeItemsMatchingService:account:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDAuthKeychain setData:forService:accessibility:account:handler:]", + "coverage": 0.9183673469387755 + }, + { + "name": "__78-[FIRInstanceIDAuthKeychain setData:forService:accessibility:account:handler:]_block_invoke", + "coverage": 0.72 + } + ] + }, + { + "name": "FIRMessagingRmqManager.m", + "coverage": 0.9473684210526315, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingRmqManager initWithDatabaseName:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager loadRmqId]", + "coverage": 0.5 + }, + { + "name": "-[FIRMessagingRmqManager loadInitialOutgoingPersistentId]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager saveRmqMessage:error:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager saveMessage:withRmqId:tag:error:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager saveLastOutgoingRmqId:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager saveS2dMessageWithRmqId:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager queryHighestRmqId]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager querylastRmqId]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager unackedS2dRmqIds]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager scanWithRmqMessageHandler:dataMessageHandler:]", + "coverage": 1 + }, + { + "name": "__71-[FIRMessagingRmqManager scanWithRmqMessageHandler:dataMessageHandler:]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager ackReceivedForRmqId:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager removeRmqMessagesWithRmqId:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager removeRmqMessagesWithRmqIds:]", + "coverage": 0.9 + }, + { + "name": "-[FIRMessagingRmqManager removeS2dIds:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager querySyncMessageWithRmqID:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager deleteSyncMessageWithRmqID:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager deleteExpiredOrFinishedSyncMessages:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager saveSyncMessageWithRmqID:expirationTime:apnsReceived:mcsReceived:error:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager updateSyncMessageViaAPNSWithRmqID:error:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager updateSyncMessageViaMCSWithRmqID:error:]", + "coverage": 1 + }, + { + "name": "+[FIRMessagingRmqManager removeDatabaseWithName:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRmqManager nextRmqId]", + "coverage": 1 + } + ] + }, + { + "name": "FIROptions.m", + "coverage": 0.9489795918367347, + "type": "objc", + "functions": [ + { + "name": "+[FIROptions defaultOptions]", + "coverage": 0.8461538461538461 + }, + { + "name": "+[FIROptions initialize]", + "coverage": 1 + }, + { + "name": "+[FIROptions defaultOptionsDictionary]", + "coverage": 1 + }, + { + "name": "+[FIROptions plistFilePathWithName:]", + "coverage": 1 + }, + { + "name": "+[FIROptions resetDefaultOptions]", + "coverage": 1 + }, + { + "name": "-[FIROptions initInternalWithOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithContentsOfFile:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithGoogleAppID:GCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions APIKey]", + "coverage": 1 + }, + { + "name": "-[FIROptions checkEditingLocked]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAPIKey:]", + "coverage": 1 + }, + { + "name": "-[FIROptions clientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions trackingID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setTrackingID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions GCMSenderID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions projectID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setProjectID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions androidClientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAndroidClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions googleAppID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGoogleAppID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions libraryVersionID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setLibraryVersionID:]", + "coverage": 0 + }, + { + "name": "-[FIROptions databaseURL]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDatabaseURL:]", + "coverage": 1 + }, + { + "name": "-[FIROptions storageBucket]", + "coverage": 1 + }, + { + "name": "-[FIROptions setStorageBucket:]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDeepLinkURLScheme:]", + "coverage": 1 + }, + { + "name": "-[FIROptions bundleID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionaryWithInfoDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIROptions isMeasurementEnabled]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIROptions isAnalyticsCollectionExpicitlySet]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionEnabled]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionDeactivated]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsEnabled]", + "coverage": 0 + }, + { + "name": "-[FIROptions isSignInEnabled]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAnalyticsConfiguration.m", + "coverage": 0.9545454545454546, + "type": "objc", + "functions": [ + { + "name": "+[FIRAnalyticsConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__43+[FIRAnalyticsConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration postNotificationName:value:]", + "coverage": 0.75 + }, + { + "name": "-[FIRAnalyticsConfiguration setMinimumSessionInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setSessionTimeoutInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:persistSetting:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDCheckinPreferences+Internal.m", + "coverage": 0.9692307692307692, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) preferencesFromKeychainContents:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) initWithDeviceID:secretToken:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) reset]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) updateWithCheckinPlistContents:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDCheckinPreferences(Internal) checkinKeychainContent]", + "coverage": 0.75 + }, + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) checkinDeviceIDFromKeychainContent:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) checkinSecretFromKeychainContent:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDCheckinPreferences(Internal) checkinKeychainContent:forIndex:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentContainer.m", + "coverage": 0.9818181818181818, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:]", + "coverage": 1 + }, + { + "name": "__55+[FIRComponentContainer registerAsComponentRegistrant:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:inSet:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:registrants:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer populateComponentsFromRegisteredClasses:forApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer instantiateInstanceForProtocol:withBlock:]", + "coverage": 0.9285714285714286 + }, + { + "name": "-[FIRComponentContainer instanceForProtocol:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer removeAllCachedInstances]", + "coverage": 1 + } + ] + }, + { + "name": "main.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "main", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDTokenStore.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRInstanceIDTokenStore defaultStore]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore initWithKeychain:]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenStore serviceKeyForAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore tokenInfoWithAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore cachedTokenInfos]", + "coverage": 1 + }, + { + "name": "+[FIRInstanceIDTokenStore tokenInfoFromKeychainItem:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore saveTokenInfo:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore removeTokenWithAuthorizedEntity:scope:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDTokenStore removeAllTokensWithHandler:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDKeyPairUtilities.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "FIRInstanceIDWebSafeBase64", + "coverage": 1 + }, + { + "name": "FIRInstanceIDSHA1", + "coverage": 1 + }, + { + "name": "FIRInstanceIDKeyPairQuery", + "coverage": 1 + }, + { + "name": "FIRInstanceIDAppIdentity", + "coverage": 1 + } + ] + }, + { + "name": "FIRInstanceIDKeyPair.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRInstanceIDKeyPair initWithPrivateKey:publicKey:publicKeyData:privateKeyData:]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair isValid]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair publicKey]", + "coverage": 1 + }, + { + "name": "-[FIRInstanceIDKeyPair privateKey]", + "coverage": 1 + } + ] + }, + { + "name": "FIRMessagingRegistrar.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRMessagingRegistrar deviceAuthID]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRegistrar secretToken]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRegistrar init]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRegistrar tryToLoadValidCheckinInfo]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRegistrar updateSubscriptionToTopic:withToken:options:shouldDelete:handler:]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRegistrar cancelAllRequests]", + "coverage": 1 + }, + { + "name": "-[FIRMessagingRegistrar doUpdateSubscriptionForTopic:token:options:shouldDelete:completion:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentType.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentType instanceForProtocol:inContainer:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponent.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponent componentWithProtocol:creationBlock:]", + "coverage": 1 + }, + { + "name": "+[FIRComponent componentWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + }, + { + "name": "-[FIRComponent initWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAppAssociationRegistration.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRAppAssociationRegistration registeredObjectWithHost:key:creationBlock:]", + "coverage": 1 + } + ] + } + ] + }, + { + "name": "OCMock.framework", + "coverage": 0.6530303030303031, + "files": [ + { + "name": "OCMExceptionReturnValueProvider.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[OCMExceptionReturnValueProvider handleInvocation:]", + "coverage": 0 + } + ] + }, + { + "name": "OCMRealObjectForwarder.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[OCMRealObjectForwarder handleInvocation:]", + "coverage": 0 + } + ] + }, + { + "name": "OCMNotificationPoster.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[OCMNotificationPoster initWithNotification:]", + "coverage": 0 + }, + { + "name": "-[OCMNotificationPoster dealloc]", + "coverage": 0 + }, + { + "name": "-[OCMNotificationPoster handleInvocation:]", + "coverage": 0 + } + ] + }, + { + "name": "OCMArgAction.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[OCMArgAction handleArgument:]", + "coverage": 0 + } + ] + }, + { + "name": "NSInvocation+OCMAdditions.m", + "coverage": 0.35990888382687924, + "type": "objc", + "functions": [ + { + "name": "+[NSInvocation(OCMAdditions) invocationForBlock:withArguments:]", + "coverage": 0.9411764705882353 + }, + { + "name": "-[NSInvocation(OCMAdditions) retainObjectArgumentsExcludingObject:]", + "coverage": 0.7794117647058824 + }, + { + "name": "-[NSInvocation(OCMAdditions) setArgumentWithObject:atIndex:]", + "coverage": 0.3090909090909091 + }, + { + "name": "-[NSInvocation(OCMAdditions) getArgumentAtIndexAsObject:]", + "coverage": 0.5669291338582677 + }, + { + "name": "-[NSInvocation(OCMAdditions) invocationDescription]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) argumentDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) objectDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) boolDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) charDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) unsignedCharDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) intDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) unsignedIntDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) shortDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) unsignedShortDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) longDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) unsignedLongDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) longLongDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) unsignedLongLongDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) doubleDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) floatDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) longDoubleDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) structDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) pointerDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) cStringDescriptionAtIndex:]", + "coverage": 0 + }, + { + "name": "-[NSInvocation(OCMAdditions) selectorDescriptionAtIndex:]", + "coverage": 0 + } + ] + }, + { + "name": "OCMFunctions.m", + "coverage": 0.43243243243243246, + "type": "objc", + "functions": [ + { + "name": "OCMTypeWithoutQualifiers", + "coverage": 1 + }, + { + "name": "OCMIsUnqualifiedClassType", + "coverage": 1 + }, + { + "name": "OCMIsUnqualifiedBlockType", + "coverage": 0.9090909090909091 + }, + { + "name": "OCMIsBlockType", + "coverage": 1 + }, + { + "name": "OCMIsObjectType", + "coverage": 0.9523809523809523 + }, + { + "name": "OCMNumberTypeForObjCType", + "coverage": 1 + }, + { + "name": "ParseStructType", + "coverage": 0 + }, + { + "name": "OCMEqualTypesAllowingOpaqueStructsInternal", + "coverage": 0.18556701030927836 + }, + { + "name": "OCMEqualTypesAllowingOpaqueStructs", + "coverage": 0.7272727272727273 + }, + { + "name": "OCMCreateSubclass", + "coverage": 1 + }, + { + "name": "OCMIsAliasSelector", + "coverage": 1 + }, + { + "name": "OCMAliasForOriginalSelector", + "coverage": 1 + }, + { + "name": "OCMOriginalSelectorForAlias", + "coverage": 0.8333333333333334 + }, + { + "name": "OCMSetAssociatedMockForClass", + "coverage": 0.8 + }, + { + "name": "OCMGetAssociatedMockForClass", + "coverage": 1 + }, + { + "name": "OCMSetAssociatedMockForObject", + "coverage": 0.8 + }, + { + "name": "OCMGetAssociatedMockForObject", + "coverage": 1 + }, + { + "name": "OCMReportFailure", + "coverage": 0 + } + ] + }, + { + "name": "OCMConstraint.m", + "coverage": 0.4426229508196721, + "type": "objc", + "functions": [ + { + "name": "+[OCMConstraint constraint]", + "coverage": 1 + }, + { + "name": "-[OCMConstraint evaluate:]", + "coverage": 0 + }, + { + "name": "-[OCMConstraint copyWithZone:]", + "coverage": 1 + }, + { + "name": "+[OCMConstraint constraintWithSelector:onObject:]", + "coverage": 0 + }, + { + "name": "+[OCMConstraint constraintWithSelector:onObject:withValue:]", + "coverage": 0 + }, + { + "name": "-[OCMAnyConstraint evaluate:]", + "coverage": 1 + }, + { + "name": "-[OCMIsNilConstraint evaluate:]", + "coverage": 1 + }, + { + "name": "-[OCMIsNotNilConstraint evaluate:]", + "coverage": 0 + }, + { + "name": "-[OCMIsNotEqualConstraint evaluate:]", + "coverage": 0 + }, + { + "name": "-[OCMInvocationConstraint evaluate:]", + "coverage": 0 + }, + { + "name": "-[OCMBlockConstraint initWithConstraintBlock:]", + "coverage": 1 + }, + { + "name": "-[OCMBlockConstraint dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMBlockConstraint evaluate:]", + "coverage": 1 + } + ] + }, + { + "name": "OCMStubRecorder.m", + "coverage": 0.5702479338842975, + "type": "objc", + "functions": [ + { + "name": "-[OCMStubRecorder init]", + "coverage": 1 + }, + { + "name": "-[OCMStubRecorder stub]", + "coverage": 1 + }, + { + "name": "-[OCMStubRecorder andReturn:]", + "coverage": 1 + }, + { + "name": "-[OCMStubRecorder andReturnValue:]", + "coverage": 1 + }, + { + "name": "-[OCMStubRecorder andThrow:]", + "coverage": 0 + }, + { + "name": "-[OCMStubRecorder andPost:]", + "coverage": 0 + }, + { + "name": "-[OCMStubRecorder andCall:onObject:]", + "coverage": 1 + }, + { + "name": "-[OCMStubRecorder andDo:]", + "coverage": 1 + }, + { + "name": "-[OCMStubRecorder andForwardToRealObject]", + "coverage": 0 + }, + { + "name": "-[OCMStubRecorder forwardInvocation:]", + "coverage": 1 + }, + { + "name": "-[OCMStubRecorder(Properties) _andReturn]", + "coverage": 1 + }, + { + "name": "__41-[OCMStubRecorder(Properties) _andReturn]_block_invoke", + "coverage": 1 + }, + { + "name": "-[OCMStubRecorder(Properties) _andThrow]", + "coverage": 0 + }, + { + "name": "__40-[OCMStubRecorder(Properties) _andThrow]_block_invoke", + "coverage": 0 + }, + { + "name": "-[OCMStubRecorder(Properties) _andPost]", + "coverage": 0 + }, + { + "name": "__39-[OCMStubRecorder(Properties) _andPost]_block_invoke", + "coverage": 0 + }, + { + "name": "-[OCMStubRecorder(Properties) _andCall]", + "coverage": 0 + }, + { + "name": "__39-[OCMStubRecorder(Properties) _andCall]_block_invoke", + "coverage": 0 + }, + { + "name": "-[OCMStubRecorder(Properties) _andDo]", + "coverage": 1 + }, + { + "name": "__37-[OCMStubRecorder(Properties) _andDo]_block_invoke", + "coverage": 1 + }, + { + "name": "-[OCMStubRecorder(Properties) _andForwardToRealObject]", + "coverage": 0 + }, + { + "name": "__54-[OCMStubRecorder(Properties) _andForwardToRealObject]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "NSMethodSignature+OCMAdditions.m", + "coverage": 0.5746268656716418, + "type": "objc", + "functions": [ + { + "name": "+[NSMethodSignature(OCMAdditions) signatureForDynamicPropertyAccessedWithSelector:inClass:]", + "coverage": 0.16666666666666666 + }, + { + "name": "+[NSMethodSignature(OCMAdditions) propertyMatchingSelector:inClass:isGetter:]", + "coverage": 0.58 + }, + { + "name": "+[NSMethodSignature(OCMAdditions) signatureForBlock:]", + "coverage": 0.9565217391304348 + }, + { + "name": "-[NSMethodSignature(OCMAdditions) usesSpecialStructureReturn]", + "coverage": 1 + }, + { + "name": "-[NSMethodSignature(OCMAdditions) fullTypeString]", + "coverage": 0 + }, + { + "name": "-[NSMethodSignature(OCMAdditions) fullObjCTypes]", + "coverage": 0 + } + ] + }, + { + "name": "OCMArg.m", + "coverage": 0.5955056179775281, + "type": "objc", + "functions": [ + { + "name": "+[OCMArg any]", + "coverage": 1 + }, + { + "name": "+[OCMArg anyPointer]", + "coverage": 1 + }, + { + "name": "+[OCMArg anyObjectRef]", + "coverage": 1 + }, + { + "name": "+[OCMArg anySelector]", + "coverage": 0 + }, + { + "name": "+[OCMArg isNil]", + "coverage": 1 + }, + { + "name": "+[OCMArg isNotNil]", + "coverage": 0 + }, + { + "name": "+[OCMArg isEqual:]", + "coverage": 1 + }, + { + "name": "+[OCMArg isNotEqual:]", + "coverage": 0 + }, + { + "name": "+[OCMArg isKindOfClass:]", + "coverage": 0 + }, + { + "name": "__24+[OCMArg isKindOfClass:]_block_invoke", + "coverage": 0 + }, + { + "name": "+[OCMArg checkWithSelector:onObject:]", + "coverage": 0 + }, + { + "name": "+[OCMArg checkWithBlock:]", + "coverage": 1 + }, + { + "name": "+[OCMArg setTo:]", + "coverage": 1 + }, + { + "name": "+[OCMArg setToValue:]", + "coverage": 0 + }, + { + "name": "+[OCMArg invokeBlock]", + "coverage": 0 + }, + { + "name": "+[OCMArg invokeBlockWithArgs:]", + "coverage": 1 + }, + { + "name": "+[OCMArg defaultValue]", + "coverage": 0 + }, + { + "name": "+[OCMArg resolveSpecialValues:]", + "coverage": 0.7368421052631579 + } + ] + }, + { + "name": "OCProtocolMockObject.m", + "coverage": 0.6153846153846154, + "type": "objc", + "functions": [ + { + "name": "-[OCProtocolMockObject initWithProtocol:]", + "coverage": 1 + }, + { + "name": "-[OCProtocolMockObject description]", + "coverage": 0 + }, + { + "name": "-[OCProtocolMockObject methodSignatureForSelector:]", + "coverage": 1 + }, + { + "name": "-[OCProtocolMockObject conformsToProtocol:]", + "coverage": 0 + }, + { + "name": "-[OCProtocolMockObject respondsToSelector:]", + "coverage": 0 + } + ] + }, + { + "name": "OCMLocation.m", + "coverage": 0.6896551724137931, + "type": "objc", + "functions": [ + { + "name": "+[OCMLocation locationWithTestCase:file:line:]", + "coverage": 1 + }, + { + "name": "-[OCMLocation initWithTestCase:file:line:]", + "coverage": 1 + }, + { + "name": "-[OCMLocation dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMLocation testCase]", + "coverage": 0 + }, + { + "name": "-[OCMLocation file]", + "coverage": 0 + }, + { + "name": "-[OCMLocation line]", + "coverage": 0 + }, + { + "name": "OCMMakeLocation", + "coverage": 1 + } + ] + }, + { + "name": "OCObserverMockObject.m", + "coverage": 0.7108433734939759, + "type": "objc", + "functions": [ + { + "name": "-[OCObserverMockObject init]", + "coverage": 1 + }, + { + "name": "-[OCObserverMockObject retain]", + "coverage": 1 + }, + { + "name": "-[OCObserverMockObject dealloc]", + "coverage": 1 + }, + { + "name": "-[OCObserverMockObject description]", + "coverage": 0 + }, + { + "name": "-[OCObserverMockObject setExpectationOrderMatters:]", + "coverage": 1 + }, + { + "name": "-[OCObserverMockObject autoRemoveFromCenter:]", + "coverage": 1 + }, + { + "name": "-[OCObserverMockObject expect]", + "coverage": 1 + }, + { + "name": "-[OCObserverMockObject verify]", + "coverage": 0 + }, + { + "name": "-[OCObserverMockObject verifyAtLocation:]", + "coverage": 0.4117647058823529 + }, + { + "name": "-[OCObserverMockObject notificationWithName:object:]", + "coverage": 0 + }, + { + "name": "-[OCObserverMockObject notificationWithName:object:userInfo:]", + "coverage": 0 + }, + { + "name": "-[OCObserverMockObject handleNotification:]", + "coverage": 0.8888888888888888 + } + ] + }, + { + "name": "OCMockObject.m", + "coverage": 0.7164179104477612, + "type": "objc", + "functions": [ + { + "name": "+[OCMockObject initialize]", + "coverage": 1 + }, + { + "name": "+[OCMockObject mockForClass:]", + "coverage": 1 + }, + { + "name": "+[OCMockObject mockForProtocol:]", + "coverage": 1 + }, + { + "name": "+[OCMockObject partialMockForObject:]", + "coverage": 1 + }, + { + "name": "+[OCMockObject niceMockForClass:]", + "coverage": 1 + }, + { + "name": "+[OCMockObject niceMockForProtocol:]", + "coverage": 1 + }, + { + "name": "+[OCMockObject _makeNice:]", + "coverage": 1 + }, + { + "name": "+[OCMockObject observerMock]", + "coverage": 1 + }, + { + "name": "-[OCMockObject init]", + "coverage": 0.782608695652174 + }, + { + "name": "-[OCMockObject dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMockObject description]", + "coverage": 0 + }, + { + "name": "-[OCMockObject addStub:]", + "coverage": 1 + }, + { + "name": "-[OCMockObject addExpectation:]", + "coverage": 1 + }, + { + "name": "-[OCMockObject setExpectationOrderMatters:]", + "coverage": 0 + }, + { + "name": "-[OCMockObject stopMocking]", + "coverage": 1 + }, + { + "name": "-[OCMockObject stub]", + "coverage": 1 + }, + { + "name": "-[OCMockObject expect]", + "coverage": 1 + }, + { + "name": "-[OCMockObject reject]", + "coverage": 1 + }, + { + "name": "-[OCMockObject verify]", + "coverage": 1 + }, + { + "name": "-[OCMockObject verifyAtLocation:]", + "coverage": 0.5897435897435898 + }, + { + "name": "-[OCMockObject verifyWithDelay:]", + "coverage": 0 + }, + { + "name": "-[OCMockObject verifyWithDelay:atLocation:]", + "coverage": 0.9583333333333334 + }, + { + "name": "-[OCMockObject verifyInvocation:]", + "coverage": 0 + }, + { + "name": "-[OCMockObject verifyInvocation:atLocation:]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[OCMockObject forwardingTargetForSelector:]", + "coverage": 1 + }, + { + "name": "-[OCMockObject handleSelector:]", + "coverage": 1 + }, + { + "name": "-[OCMockObject forwardInvocation:]", + "coverage": 0.8695652173913043 + }, + { + "name": "-[OCMockObject handleInvocation:]", + "coverage": 0.9354838709677419 + }, + { + "name": "-[OCMockObject _nextExpectedInvocation]", + "coverage": 1 + }, + { + "name": "-[OCMockObject handleUnRecordedInvocation:]", + "coverage": 0.5714285714285714 + }, + { + "name": "-[OCMockObject doesNotRecognizeSelector:]", + "coverage": 0 + }, + { + "name": "-[OCMockObject _stubDescriptions:]", + "coverage": 0 + } + ] + }, + { + "name": "OCMReturnValueProvider.m", + "coverage": 0.72, + "type": "objc", + "functions": [ + { + "name": "-[OCMReturnValueProvider initWithValue:]", + "coverage": 1 + }, + { + "name": "-[OCMReturnValueProvider dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMReturnValueProvider handleInvocation:]", + "coverage": 0.46153846153846156 + } + ] + }, + { + "name": "OCClassMockObject.m", + "coverage": 0.7669902912621359, + "type": "objc", + "functions": [ + { + "name": "-[OCClassMockObject initWithClass:]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject dealloc]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject description]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject mockedClass]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject stopMocking]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject stopMockingClassMethods]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject addStub:]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject prepareClassForClassMethodMocking]", + "coverage": 0.9649122807017544 + }, + { + "name": "__54-[OCClassMockObject prepareClassForClassMethodMocking]_block_invoke", + "coverage": 0.9473684210526315 + }, + { + "name": "-[OCClassMockObject setupForwarderForClassMethodSelector:]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject forwardInvocationForClassObject:]", + "coverage": 0.8461538461538461 + }, + { + "name": "-[OCClassMockObject initializeForClassObject]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject methodSignatureForSelector:]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject mockObjectClass]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject class]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject respondsToSelector:]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject isKindOfClass:]", + "coverage": 1 + }, + { + "name": "-[OCClassMockObject conformsToProtocol:]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSValue__]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSTimeZone__]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSSet__]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSOrderedSet__]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSNumber__]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSDate__]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSString__]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSDictionary__]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSData__]", + "coverage": 0 + }, + { + "name": "-[OCClassMockObject(NSIsKindsImplementation) isNSArray__]", + "coverage": 0 + } + ] + }, + { + "name": "OCMObserverRecorder.m", + "coverage": 0.7692307692307693, + "type": "objc", + "functions": [ + { + "name": "-[OCMObserverRecorder dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMObserverRecorder notificationWithName:object:]", + "coverage": 0 + }, + { + "name": "-[OCMObserverRecorder notificationWithName:object:userInfo:]", + "coverage": 1 + }, + { + "name": "-[OCMObserverRecorder matchesNotification:]", + "coverage": 1 + }, + { + "name": "-[OCMObserverRecorder argument:matchesArgument:]", + "coverage": 0.7727272727272727 + } + ] + }, + { + "name": "OCMInvocationExpectation.m", + "coverage": 0.8, + "type": "objc", + "functions": [ + { + "name": "-[OCMInvocationExpectation setMatchAndReject:]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationExpectation isMatchAndReject]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationExpectation isSatisfied]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationExpectation handleInvocation:]", + "coverage": 0.6428571428571429 + } + ] + }, + { + "name": "OCMIndirectReturnValueProvider.m", + "coverage": 0.8333333333333334, + "type": "objc", + "functions": [ + { + "name": "-[OCMIndirectReturnValueProvider initWithProvider:andSelector:]", + "coverage": 1 + }, + { + "name": "-[OCMIndirectReturnValueProvider dealloc]", + "coverage": 0 + }, + { + "name": "-[OCMIndirectReturnValueProvider handleInvocation:]", + "coverage": 1 + } + ] + }, + { + "name": "OCMRecorder.m", + "coverage": 0.8363636363636363, + "type": "objc", + "functions": [ + { + "name": "-[OCMRecorder init]", + "coverage": 1 + }, + { + "name": "-[OCMRecorder initWithMockObject:]", + "coverage": 1 + }, + { + "name": "-[OCMRecorder setMockObject:]", + "coverage": 1 + }, + { + "name": "-[OCMRecorder dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMRecorder description]", + "coverage": 0 + }, + { + "name": "-[OCMRecorder invocationMatcher]", + "coverage": 0 + }, + { + "name": "-[OCMRecorder classMethod]", + "coverage": 1 + }, + { + "name": "-[OCMRecorder ignoringNonObjectArgs]", + "coverage": 1 + }, + { + "name": "-[OCMRecorder methodSignatureForSelector:]", + "coverage": 1 + }, + { + "name": "-[OCMRecorder forwardInvocation:]", + "coverage": 1 + }, + { + "name": "-[OCMRecorder doesNotRecognizeSelector:]", + "coverage": 0 + } + ] + }, + { + "name": "OCMBoxedReturnValueProvider.m", + "coverage": 0.84375, + "type": "objc", + "functions": [ + { + "name": "-[OCMBoxedReturnValueProvider handleInvocation:]", + "coverage": 0.7619047619047619 + }, + { + "name": "-[OCMBoxedReturnValueProvider isMethodReturnType:compatibleWithValueType:]", + "coverage": 1 + } + ] + }, + { + "name": "OCMInvocationMatcher.m", + "coverage": 0.9148936170212766, + "type": "objc", + "functions": [ + { + "name": "-[OCMInvocationMatcher dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationMatcher setInvocation:]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationMatcher setRecordedAsClassMethod:]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationMatcher recordedAsClassMethod]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationMatcher setIgnoreNonObjectArgs:]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationMatcher description]", + "coverage": 0 + }, + { + "name": "-[OCMInvocationMatcher recordedInvocation]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationMatcher matchesSelector:]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationMatcher matchesInvocation:]", + "coverage": 0.9122807017543859 + } + ] + }, + { + "name": "OCMInvocationStub.m", + "coverage": 0.918918918918919, + "type": "objc", + "functions": [ + { + "name": "-[OCMInvocationStub init]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationStub dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationStub addInvocationAction:]", + "coverage": 1 + }, + { + "name": "-[OCMInvocationStub invocationActions]", + "coverage": 0 + }, + { + "name": "-[OCMInvocationStub handleInvocation:]", + "coverage": 1 + } + ] + }, + { + "name": "OCMPassByRefSetter.m", + "coverage": 0.9545454545454546, + "type": "objc", + "functions": [ + { + "name": "-[OCMPassByRefSetter initWithValue:]", + "coverage": 1 + }, + { + "name": "-[OCMPassByRefSetter dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMPassByRefSetter handleArgument:]", + "coverage": 0.9 + } + ] + }, + { + "name": "OCPartialMockObject.m", + "coverage": 0.9560439560439561, + "type": "objc", + "functions": [ + { + "name": "-[OCPartialMockObject initWithObject:]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject dealloc]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject description]", + "coverage": 0 + }, + { + "name": "-[OCPartialMockObject realObject]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject assertClassIsSupported:]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[OCPartialMockObject classToSubclassForObject:]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject stopMocking]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject addStub:]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject handleUnRecordedInvocation:]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject prepareObjectForInstanceMethodMocking]", + "coverage": 1 + }, + { + "name": "__60-[OCPartialMockObject prepareObjectForInstanceMethodMocking]_block_invoke", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject setupForwarderForSelector:]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject classForRealObject]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject forwardingTargetForSelectorForRealObject:]", + "coverage": 1 + }, + { + "name": "-[OCPartialMockObject ocmock_replaced_forwardingTargetForSelector:]", + "coverage": 0 + }, + { + "name": "-[OCPartialMockObject forwardInvocationForRealObject:]", + "coverage": 1 + } + ] + }, + { + "name": "NSValue+OCMAdditions.m", + "coverage": 0.9649122807017544, + "type": "objc", + "functions": [ + { + "name": "OCMNumberForValue", + "coverage": 1 + }, + { + "name": "-[NSValue(OCMAdditions) getBytes:objCType:]", + "coverage": 0.9459459459459459 + } + ] + }, + { + "name": "OCMBlockCaller.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[OCMBlockCaller initWithCallBlock:]", + "coverage": 1 + }, + { + "name": "-[OCMBlockCaller dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMBlockCaller handleInvocation:]", + "coverage": 1 + } + ] + }, + { + "name": "OCMMacroState.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[OCMMacroState beginStubMacro]", + "coverage": 1 + }, + { + "name": "+[OCMMacroState endStubMacro]", + "coverage": 1 + }, + { + "name": "+[OCMMacroState beginExpectMacro]", + "coverage": 1 + }, + { + "name": "+[OCMMacroState endExpectMacro]", + "coverage": 1 + }, + { + "name": "+[OCMMacroState beginRejectMacro]", + "coverage": 1 + }, + { + "name": "+[OCMMacroState endRejectMacro]", + "coverage": 1 + }, + { + "name": "+[OCMMacroState beginVerifyMacroAtLocation:]", + "coverage": 1 + }, + { + "name": "+[OCMMacroState endVerifyMacro]", + "coverage": 1 + }, + { + "name": "+[OCMMacroState globalState]", + "coverage": 1 + }, + { + "name": "-[OCMMacroState initWithRecorder:]", + "coverage": 1 + }, + { + "name": "-[OCMMacroState dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMMacroState recorder]", + "coverage": 1 + }, + { + "name": "-[OCMMacroState switchToClassMethod]", + "coverage": 1 + } + ] + }, + { + "name": "NSNotificationCenter+OCMAdditions.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[NSNotificationCenter(OCMAdditions) addMockObserver:name:object:]", + "coverage": 1 + } + ] + }, + { + "name": "OCMBlockArgCaller.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[OCMBlockArgCaller initWithBlockArguments:]", + "coverage": 1 + }, + { + "name": "-[OCMBlockArgCaller dealloc]", + "coverage": 1 + }, + { + "name": "-[OCMBlockArgCaller copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[OCMBlockArgCaller handleArgument:]", + "coverage": 1 + } + ] + }, + { + "name": "NSObject+OCMAdditions.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[NSObject(OCMAdditions) instanceMethodForwarderForSelector:]", + "coverage": 1 + }, + { + "name": "+[NSObject(OCMAdditions) enumerateMethodsInClass:usingBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "OCMVerifier.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[OCMVerifier init]", + "coverage": 1 + }, + { + "name": "-[OCMVerifier forwardInvocation:]", + "coverage": 1 + }, + { + "name": "-[OCMVerifier dealloc]", + "coverage": 1 + } + ] + }, + { + "name": "OCMExpectationRecorder.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[OCMExpectationRecorder init]", + "coverage": 1 + }, + { + "name": "-[OCMExpectationRecorder expectation]", + "coverage": 1 + }, + { + "name": "-[OCMExpectationRecorder never]", + "coverage": 1 + }, + { + "name": "-[OCMExpectationRecorder forwardInvocation:]", + "coverage": 1 + } + ] + } + ] + }, + { + "name": "Protobuf.framework", + "coverage": 0.09864249142749777, + "files": [ + { + "name": "Any.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBAnyRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "+[GPBAny descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "Wrappers.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBWrappersRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "+[GPBDoubleValue descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBFloatValue descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBInt64Value descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBUInt64Value descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBInt32Value descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBUInt32Value descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBBoolValue descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBStringValue descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBBytesValue descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "Type.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBTypeRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "GPBSyntax_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GPBSyntax_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GPBType descriptor]", + "coverage": 0 + }, + { + "name": "GPBType_Syntax_RawValue", + "coverage": 0 + }, + { + "name": "SetGPBType_Syntax_RawValue", + "coverage": 0 + }, + { + "name": "+[GPBField descriptor]", + "coverage": 0 + }, + { + "name": "GPBField_Kind_RawValue", + "coverage": 0 + }, + { + "name": "SetGPBField_Kind_RawValue", + "coverage": 0 + }, + { + "name": "GPBField_Cardinality_RawValue", + "coverage": 0 + }, + { + "name": "SetGPBField_Cardinality_RawValue", + "coverage": 0 + }, + { + "name": "GPBField_Kind_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GPBField_Kind_IsValidValue", + "coverage": 0 + }, + { + "name": "GPBField_Cardinality_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GPBField_Cardinality_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GPBEnum descriptor]", + "coverage": 0 + }, + { + "name": "GPBEnum_Syntax_RawValue", + "coverage": 0 + }, + { + "name": "SetGPBEnum_Syntax_RawValue", + "coverage": 0 + }, + { + "name": "+[GPBEnumValue descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBOption descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "Timestamp.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBTimestampRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "+[GPBTimestamp descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "Struct.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBStructRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "GPBNullValue_EnumDescriptor", + "coverage": 0 + }, + { + "name": "GPBNullValue_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GPBStruct descriptor]", + "coverage": 0 + }, + { + "name": "+[GPBValue descriptor]", + "coverage": 0 + }, + { + "name": "GPBValue_NullValue_RawValue", + "coverage": 0 + }, + { + "name": "SetGPBValue_NullValue_RawValue", + "coverage": 0 + }, + { + "name": "GPBValue_ClearKindOneOfCase", + "coverage": 0 + }, + { + "name": "+[GPBListValue descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "GPBDictionary.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "DictDefault_IsValidValue", + "coverage": 0 + }, + { + "name": "ComputeDictInt32FieldSize", + "coverage": 0 + }, + { + "name": "WriteDictInt32Field", + "coverage": 0 + }, + { + "name": "ComputeDictUInt32FieldSize", + "coverage": 0 + }, + { + "name": "WriteDictUInt32Field", + "coverage": 0 + }, + { + "name": "ComputeDictInt64FieldSize", + "coverage": 0 + }, + { + "name": "WriteDictInt64Field", + "coverage": 0 + }, + { + "name": "ComputeDictUInt64FieldSize", + "coverage": 0 + }, + { + "name": "WriteDictUInt64Field", + "coverage": 0 + }, + { + "name": "ComputeDictBoolFieldSize", + "coverage": 0 + }, + { + "name": "WriteDictBoolField", + "coverage": 0 + }, + { + "name": "ComputeDictEnumFieldSize", + "coverage": 0 + }, + { + "name": "WriteDictEnumField", + "coverage": 0 + }, + { + "name": "ComputeDictFloatFieldSize", + "coverage": 0 + }, + { + "name": "WriteDictFloatField", + "coverage": 0 + }, + { + "name": "ComputeDictDoubleFieldSize", + "coverage": 0 + }, + { + "name": "WriteDictDoubleField", + "coverage": 0 + }, + { + "name": "ComputeDictStringFieldSize", + "coverage": 0 + }, + { + "name": "WriteDictStringField", + "coverage": 0 + }, + { + "name": "ComputeDictObjectFieldSize", + "coverage": 0 + }, + { + "name": "WriteDictObjectField", + "coverage": 0 + }, + { + "name": "GPBDictionaryComputeSizeInternalHelper", + "coverage": 0 + }, + { + "name": "GPBDictionaryWriteToStreamInternalHelper", + "coverage": 0 + }, + { + "name": "GPBDictionaryIsInitializedInternalHelper", + "coverage": 0 + }, + { + "name": "ReadValue", + "coverage": 0 + }, + { + "name": "GPBDictionaryReadEntry", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary initWithUInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary enumerateKeysAndUInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBUInt32UInt32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary getUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary setUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary removeUInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary initWithInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary enumerateKeysAndInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBUInt32Int32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary getInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary setInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary removeInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary initWithUInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary enumerateKeysAndUInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBUInt32UInt64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary getUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary setUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary removeUInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32UInt64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary initWithInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary enumerateKeysAndInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBUInt32Int64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary getInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary setInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary removeInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Int64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary initWithBools:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary enumerateKeysAndBoolsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBUInt32BoolDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary getBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary setBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary removeBoolForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32BoolDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary initWithFloats:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary enumerateKeysAndFloatsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBUInt32FloatDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary getFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary setFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary removeFloatForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32FloatDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary initWithDoubles:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary enumerateKeysAndDoublesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBUInt32DoubleDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary getDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary setDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary removeDoubleForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32DoubleDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary initWithValidationFunction:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary initWithValidationFunction:rawValues:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary initWithValidationFunction:capacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary enumerateKeysAndRawValuesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary serializedDataForUnknownValue:forKey:keyDataType:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBUInt32EnumDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary getEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary getRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary enumerateKeysAndEnumsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary addRawEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary setRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary removeEnumForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32EnumDictionary setEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary initWithObjects:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary enumerateKeysAndObjectsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary isInitialized]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary deepCopyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBUInt32ObjectDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary objectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary setObject:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary removeObjectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32ObjectDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary initWithUInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary enumerateKeysAndUInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBInt32UInt32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary getUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary setUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary removeUInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary initWithInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary enumerateKeysAndInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBInt32Int32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary getInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary setInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary removeInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary initWithUInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary enumerateKeysAndUInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBInt32UInt64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary getUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary setUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary removeUInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32UInt64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary initWithInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary enumerateKeysAndInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBInt32Int64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary getInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary setInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary removeInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Int64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary initWithBools:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary enumerateKeysAndBoolsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__49-[GPBInt32BoolDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary getBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary setBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary removeBoolForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32BoolDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary initWithFloats:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary enumerateKeysAndFloatsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBInt32FloatDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary getFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary setFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary removeFloatForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32FloatDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary initWithDoubles:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary enumerateKeysAndDoublesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBInt32DoubleDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary getDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary setDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary removeDoubleForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32DoubleDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary initWithValidationFunction:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary initWithValidationFunction:rawValues:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary initWithValidationFunction:capacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary enumerateKeysAndRawValuesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary serializedDataForUnknownValue:forKey:keyDataType:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__49-[GPBInt32EnumDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary getEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary getRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary enumerateKeysAndEnumsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary addRawEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary setRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary removeEnumForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32EnumDictionary setEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary initWithObjects:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary enumerateKeysAndObjectsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary isInitialized]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary deepCopyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBInt32ObjectDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary objectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary setObject:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary removeObjectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32ObjectDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary initWithUInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary enumerateKeysAndUInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBUInt64UInt32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary getUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary setUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary removeUInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary initWithInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary enumerateKeysAndInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBUInt64Int32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary getInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary setInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary removeInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary initWithUInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary enumerateKeysAndUInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBUInt64UInt64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary getUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary setUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary removeUInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64UInt64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary initWithInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary enumerateKeysAndInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBUInt64Int64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary getInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary setInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary removeInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Int64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary initWithBools:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary enumerateKeysAndBoolsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBUInt64BoolDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary getBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary setBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary removeBoolForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64BoolDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary initWithFloats:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary enumerateKeysAndFloatsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBUInt64FloatDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary getFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary setFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary removeFloatForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64FloatDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary initWithDoubles:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary enumerateKeysAndDoublesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBUInt64DoubleDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary getDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary setDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary removeDoubleForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64DoubleDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary initWithValidationFunction:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary initWithValidationFunction:rawValues:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary initWithValidationFunction:capacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary enumerateKeysAndRawValuesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary serializedDataForUnknownValue:forKey:keyDataType:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBUInt64EnumDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary getEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary getRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary enumerateKeysAndEnumsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary addRawEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary setRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary removeEnumForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64EnumDictionary setEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary initWithObjects:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary enumerateKeysAndObjectsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary isInitialized]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary deepCopyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBUInt64ObjectDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary objectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary setObject:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary removeObjectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64ObjectDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary initWithUInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary enumerateKeysAndUInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBInt64UInt32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary getUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary setUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary removeUInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary initWithInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary enumerateKeysAndInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBInt64Int32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary getInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary setInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary removeInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary initWithUInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary enumerateKeysAndUInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBInt64UInt64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary getUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary setUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary removeUInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64UInt64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary initWithInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary enumerateKeysAndInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBInt64Int64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary getInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary setInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary removeInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Int64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary initWithBools:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary enumerateKeysAndBoolsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__49-[GPBInt64BoolDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary getBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary setBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary removeBoolForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64BoolDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary initWithFloats:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary enumerateKeysAndFloatsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBInt64FloatDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary getFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary setFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary removeFloatForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64FloatDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary initWithDoubles:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary enumerateKeysAndDoublesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBInt64DoubleDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary getDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary setDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary removeDoubleForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64DoubleDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary initWithValidationFunction:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary initWithValidationFunction:rawValues:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary initWithValidationFunction:capacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary enumerateKeysAndRawValuesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary serializedDataForUnknownValue:forKey:keyDataType:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__49-[GPBInt64EnumDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary getEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary getRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary enumerateKeysAndEnumsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary addRawEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary setRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary removeEnumForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64EnumDictionary setEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary initWithObjects:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary enumerateKeysAndObjectsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary isInitialized]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary deepCopyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBInt64ObjectDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary objectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary setObject:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary removeObjectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64ObjectDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary initWithUInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary enumerateKeysAndUInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBStringUInt32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary getUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary setUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary removeUInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary initWithInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary enumerateKeysAndInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBStringInt32Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary getInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary setInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary removeInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary initWithUInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary enumerateKeysAndUInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBStringUInt64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary getUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary setUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary removeUInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringUInt64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary initWithInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary enumerateKeysAndInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBStringInt64Dictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary getInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary setInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary removeInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringInt64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary initWithBools:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary enumerateKeysAndBoolsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBStringBoolDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary getBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary setBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary removeBoolForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringBoolDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary initWithFloats:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary enumerateKeysAndFloatsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__51-[GPBStringFloatDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary getFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary setFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary removeFloatForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringFloatDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary initWithDoubles:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary enumerateKeysAndDoublesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__52-[GPBStringDoubleDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary getDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary setDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary removeDoubleForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringDoubleDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary initWithValidationFunction:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary initWithValidationFunction:rawValues:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary initWithValidationFunction:capacity:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary enumerateKeysAndRawValuesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary serializedDataForUnknownValue:forKey:keyDataType:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "__50-[GPBStringEnumDictionary enumerateForTextFormat:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary getEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary getRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary enumerateKeysAndEnumsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary addRawEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary setRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary removeEnumForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBStringEnumDictionary setEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary initWithUInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary getUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary enumerateKeysAndUInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary setUInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary removeUInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary initWithInt32s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary getInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary enumerateKeysAndInt32sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary setInt32:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary removeInt32ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt32Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary initWithUInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary getUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary enumerateKeysAndUInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary setUInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary removeUInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolUInt64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary initWithInt64s:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary getInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary enumerateKeysAndInt64sUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary setInt64:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary removeInt64ForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolInt64Dictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary initWithBools:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary getBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary enumerateKeysAndBoolsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary setBool:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary removeBoolForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolBoolDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary initWithFloats:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary getFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary enumerateKeysAndFloatsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary setFloat:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary removeFloatForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolFloatDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary initWithDoubles:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary getDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary enumerateKeysAndDoublesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary setDouble:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary removeDoubleForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolDoubleDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary initWithObjects:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary objectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary enumerateKeysAndObjectsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary isInitialized]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary deepCopyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary addEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary setObject:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary removeObjectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolObjectDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary initWithValidationFunction:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary initWithValidationFunction:rawValues:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary initWithDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary initWithValidationFunction:capacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary getEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary getRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary enumerateKeysAndRawValuesUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary enumerateKeysAndEnumsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary serializedDataForUnknownValue:forKey:keyDataType:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary computeSerializedSizeAsField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary writeToCodedOutputStream:asField:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary enumerateForTextFormat:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary setGPBGenericValue:forGPBGenericValueKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary addRawEntriesFromDictionary:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary setEnum:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary setRawValue:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary removeEnumForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolEnumDictionary removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary initWithObjects:forKeys:count:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary count]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary objectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary keyEnumerator]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary setObject:forKey:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary removeObjectForKey:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary mutableCopyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary objectForKeyedSubscript:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary setObject:forKeyedSubscript:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary enumerateKeysAndObjectsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedDictionary enumerateKeysAndObjectsWithOptions:usingBlock:]", + "coverage": 0 + } + ] + }, + { + "name": "GPBExtensionInternals.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "DataTypeSize", + "coverage": 0 + }, + { + "name": "ComputePBSerializedSizeNoTagOfObject", + "coverage": 0 + }, + { + "name": "ComputeSerializedSizeIncludingTagOfObject", + "coverage": 0 + }, + { + "name": "ComputeSerializedSizeIncludingTagOfArray", + "coverage": 0 + }, + { + "name": "WriteObjectIncludingTagToCodedOutputStream", + "coverage": 0 + }, + { + "name": "WriteObjectNoTagToCodedOutputStream", + "coverage": 0 + }, + { + "name": "WriteArrayIncludingTagsToCodedOutputStream", + "coverage": 0 + }, + { + "name": "GPBExtensionMergeFromInputStream", + "coverage": 0 + }, + { + "name": "GPBWriteExtensionValueToOutputStream", + "coverage": 0 + }, + { + "name": "GPBComputeExtensionSerializedSizeIncludingTag", + "coverage": 0 + }, + { + "name": "NewSingleValueFromInputStream", + "coverage": 0 + } + ] + }, + { + "name": "SourceContext.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBSourceContextRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "+[GPBSourceContext descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "FieldMask.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBFieldMaskRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "+[GPBFieldMask descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "Empty.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBEmptyRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "+[GPBEmpty descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "GPBUnknownField.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[GPBUnknownField initWithNumber:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField hash]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField writeToOutput:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField serializedSize]", + "coverage": 0 + }, + { + "name": "__33-[GPBUnknownField serializedSize]_block_invoke", + "coverage": 0 + }, + { + "name": "__33-[GPBUnknownField serializedSize]_block_invoke.42", + "coverage": 0 + }, + { + "name": "__33-[GPBUnknownField serializedSize]_block_invoke.47", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField writeAsMessageSetExtensionToOutput:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField serializedSizeAsMessageSetExtension]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField description]", + "coverage": 0 + }, + { + "name": "__30-[GPBUnknownField description]_block_invoke", + "coverage": 0 + }, + { + "name": "__30-[GPBUnknownField description]_block_invoke.64", + "coverage": 0 + }, + { + "name": "__30-[GPBUnknownField description]_block_invoke.70", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField mergeFromField:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField addVarint:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField addFixed32:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField addFixed64:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField addLengthDelimited:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownField addGroup:]", + "coverage": 0 + } + ] + }, + { + "name": "Duration.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBDurationRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "+[GPBDuration descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "Api.pbobjc.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "GPBApiRoot_FileDescriptor", + "coverage": 0 + }, + { + "name": "+[GPBApi descriptor]", + "coverage": 0 + }, + { + "name": "GPBApi_Syntax_RawValue", + "coverage": 0 + }, + { + "name": "SetGPBApi_Syntax_RawValue", + "coverage": 0 + }, + { + "name": "+[GPBMethod descriptor]", + "coverage": 0 + }, + { + "name": "GPBMethod_Syntax_RawValue", + "coverage": 0 + }, + { + "name": "SetGPBMethod_Syntax_RawValue", + "coverage": 0 + }, + { + "name": "+[GPBMixin descriptor]", + "coverage": 0 + } + ] + }, + { + "name": "GPBWellKnownTypes.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "TimeIntervalFromSecondsAndNanos", + "coverage": 0 + }, + { + "name": "SecondsAndNanosFromTimeInterval", + "coverage": 0 + }, + { + "name": "BuildTypeURL", + "coverage": 0 + }, + { + "name": "ParseTypeFromURL", + "coverage": 0 + }, + { + "name": "-[GPBTimestamp(GBPWellKnownTypes) initWithDate:]", + "coverage": 0 + }, + { + "name": "-[GPBTimestamp(GBPWellKnownTypes) initWithTimeIntervalSince1970:]", + "coverage": 0 + }, + { + "name": "-[GPBTimestamp(GBPWellKnownTypes) date]", + "coverage": 0 + }, + { + "name": "-[GPBTimestamp(GBPWellKnownTypes) setDate:]", + "coverage": 0 + }, + { + "name": "-[GPBTimestamp(GBPWellKnownTypes) timeIntervalSince1970]", + "coverage": 0 + }, + { + "name": "-[GPBTimestamp(GBPWellKnownTypes) setTimeIntervalSince1970:]", + "coverage": 0 + }, + { + "name": "-[GPBDuration(GBPWellKnownTypes) initWithTimeInterval:]", + "coverage": 0 + }, + { + "name": "-[GPBDuration(GBPWellKnownTypes) initWithTimeIntervalSince1970:]", + "coverage": 0 + }, + { + "name": "-[GPBDuration(GBPWellKnownTypes) timeInterval]", + "coverage": 0 + }, + { + "name": "-[GPBDuration(GBPWellKnownTypes) setTimeInterval:]", + "coverage": 0 + }, + { + "name": "-[GPBDuration(GBPWellKnownTypes) timeIntervalSince1970]", + "coverage": 0 + }, + { + "name": "-[GPBDuration(GBPWellKnownTypes) setTimeIntervalSince1970:]", + "coverage": 0 + }, + { + "name": "+[GPBAny(GBPWellKnownTypes) anyWithMessage:error:]", + "coverage": 0 + }, + { + "name": "+[GPBAny(GBPWellKnownTypes) anyWithMessage:typeURLPrefix:error:]", + "coverage": 0 + }, + { + "name": "-[GPBAny(GBPWellKnownTypes) initWithMessage:error:]", + "coverage": 0 + }, + { + "name": "-[GPBAny(GBPWellKnownTypes) initWithMessage:typeURLPrefix:error:]", + "coverage": 0 + }, + { + "name": "-[GPBAny(GBPWellKnownTypes) packWithMessage:error:]", + "coverage": 0 + }, + { + "name": "-[GPBAny(GBPWellKnownTypes) packWithMessage:typeURLPrefix:error:]", + "coverage": 0 + }, + { + "name": "-[GPBAny(GBPWellKnownTypes) unpackMessageClass:error:]", + "coverage": 0 + } + ] + }, + { + "name": "GPBArray.m", + "coverage": 0.01252723311546841, + "type": "objc", + "functions": [ + { + "name": "ArrayDefault_IsValidValue", + "coverage": 0 + }, + { + "name": "+[GPBInt32Array array]", + "coverage": 0 + }, + { + "name": "+[GPBInt32Array arrayWithValue:]", + "coverage": 0 + }, + { + "name": "+[GPBInt32Array arrayWithValueArray:]", + "coverage": 0 + }, + { + "name": "+[GPBInt32Array arrayWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array init]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array initWithValueArray:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array initWithValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array description]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array enumerateValuesWithBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array enumerateValuesWithOptions:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array valueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array internalResizeToCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array addValue:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array addValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array insertValue:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array replaceValueAtIndex:withValue:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array addValuesFromArray:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array removeValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt32Array exchangeValueAtIndex:withValueAtIndex:]", + "coverage": 0 + }, + { + "name": "+[GPBUInt32Array array]", + "coverage": 0 + }, + { + "name": "+[GPBUInt32Array arrayWithValue:]", + "coverage": 0 + }, + { + "name": "+[GPBUInt32Array arrayWithValueArray:]", + "coverage": 0 + }, + { + "name": "+[GPBUInt32Array arrayWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array initWithValueArray:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array initWithValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array enumerateValuesWithBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array enumerateValuesWithOptions:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array valueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array internalResizeToCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array addValue:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array addValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array insertValue:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array replaceValueAtIndex:withValue:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array addValuesFromArray:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array removeValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt32Array exchangeValueAtIndex:withValueAtIndex:]", + "coverage": 0 + }, + { + "name": "+[GPBInt64Array array]", + "coverage": 0 + }, + { + "name": "+[GPBInt64Array arrayWithValue:]", + "coverage": 0 + }, + { + "name": "+[GPBInt64Array arrayWithValueArray:]", + "coverage": 0 + }, + { + "name": "+[GPBInt64Array arrayWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array init]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array initWithValueArray:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array initWithValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array hash]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array description]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array enumerateValuesWithBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array enumerateValuesWithOptions:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array valueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array internalResizeToCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array addValue:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array addValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array insertValue:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array replaceValueAtIndex:withValue:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array addValuesFromArray:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array removeValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBInt64Array exchangeValueAtIndex:withValueAtIndex:]", + "coverage": 0 + }, + { + "name": "+[GPBUInt64Array array]", + "coverage": 0 + }, + { + "name": "+[GPBUInt64Array arrayWithValue:]", + "coverage": 0 + }, + { + "name": "+[GPBUInt64Array arrayWithValueArray:]", + "coverage": 0 + }, + { + "name": "+[GPBUInt64Array arrayWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array init]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array initWithValueArray:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array initWithValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array hash]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array description]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array enumerateValuesWithBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array enumerateValuesWithOptions:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array valueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array internalResizeToCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array addValue:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array addValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array insertValue:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array replaceValueAtIndex:withValue:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array addValuesFromArray:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array removeValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBUInt64Array exchangeValueAtIndex:withValueAtIndex:]", + "coverage": 0 + }, + { + "name": "+[GPBFloatArray array]", + "coverage": 0 + }, + { + "name": "+[GPBFloatArray arrayWithValue:]", + "coverage": 0 + }, + { + "name": "+[GPBFloatArray arrayWithValueArray:]", + "coverage": 0 + }, + { + "name": "+[GPBFloatArray arrayWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray init]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray initWithValueArray:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray initWithValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray hash]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray description]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray enumerateValuesWithBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray enumerateValuesWithOptions:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray valueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray internalResizeToCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray addValue:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray addValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray insertValue:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray replaceValueAtIndex:withValue:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray addValuesFromArray:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray removeValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBFloatArray exchangeValueAtIndex:withValueAtIndex:]", + "coverage": 0 + }, + { + "name": "+[GPBDoubleArray array]", + "coverage": 0 + }, + { + "name": "+[GPBDoubleArray arrayWithValue:]", + "coverage": 0 + }, + { + "name": "+[GPBDoubleArray arrayWithValueArray:]", + "coverage": 0 + }, + { + "name": "+[GPBDoubleArray arrayWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray init]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray initWithValueArray:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray initWithValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray hash]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray description]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray enumerateValuesWithBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray enumerateValuesWithOptions:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray valueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray internalResizeToCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray addValue:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray addValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray insertValue:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray replaceValueAtIndex:withValue:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray addValuesFromArray:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray removeValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBDoubleArray exchangeValueAtIndex:withValueAtIndex:]", + "coverage": 0 + }, + { + "name": "+[GPBBoolArray array]", + "coverage": 0 + }, + { + "name": "+[GPBBoolArray arrayWithValue:]", + "coverage": 0 + }, + { + "name": "+[GPBBoolArray arrayWithValueArray:]", + "coverage": 0 + }, + { + "name": "+[GPBBoolArray arrayWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray init]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray initWithValueArray:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray initWithValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray initWithCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray hash]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray description]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray enumerateValuesWithBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray enumerateValuesWithOptions:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray valueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray internalResizeToCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray addValue:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray addValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray insertValue:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray replaceValueAtIndex:withValue:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray addValuesFromArray:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray removeValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBBoolArray exchangeValueAtIndex:withValueAtIndex:]", + "coverage": 0 + }, + { + "name": "+[GPBEnumArray array]", + "coverage": 0 + }, + { + "name": "+[GPBEnumArray arrayWithValidationFunction:]", + "coverage": 0 + }, + { + "name": "+[GPBEnumArray arrayWithValidationFunction:rawValue:]", + "coverage": 0 + }, + { + "name": "+[GPBEnumArray arrayWithValueArray:]", + "coverage": 0 + }, + { + "name": "+[GPBEnumArray arrayWithValidationFunction:capacity:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray init]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray initWithValueArray:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray initWithValidationFunction:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray initWithValidationFunction:rawValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray initWithValidationFunction:capacity:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray hash]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray description]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray enumerateRawValuesWithBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray enumerateRawValuesWithOptions:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray valueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray rawValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray enumerateValuesWithBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray enumerateValuesWithOptions:usingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray internalResizeToCapacity:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray addRawValue:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray addRawValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray insertRawValue:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray replaceValueAtIndex:withRawValue:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray addRawValuesFromArray:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray removeValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray removeAll]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray exchangeValueAtIndex:withValueAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray addValue:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray addValues:count:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray insertValue:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumArray replaceValueAtIndex:withValue:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray dealloc]", + "coverage": 1 + }, + { + "name": "-[GPBAutocreatedArray count]", + "coverage": 1 + }, + { + "name": "-[GPBAutocreatedArray objectAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray insertObject:atIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray removeObject:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray removeObjectAtIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray addObject:]", + "coverage": 1 + }, + { + "name": "-[GPBAutocreatedArray removeLastObject]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray replaceObjectAtIndex:withObject:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray mutableCopyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray countByEnumeratingWithState:objects:count:]", + "coverage": 1 + }, + { + "name": "-[GPBAutocreatedArray enumerateObjectsUsingBlock:]", + "coverage": 0 + }, + { + "name": "-[GPBAutocreatedArray enumerateObjectsWithOptions:usingBlock:]", + "coverage": 0 + } + ] + }, + { + "name": "GPBExtensionRegistry.m", + "coverage": 0.06666666666666667, + "type": "objc", + "functions": [ + { + "name": "-[GPBExtensionRegistry init]", + "coverage": 1 + }, + { + "name": "-[GPBExtensionRegistry dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionRegistry copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionRegistry addExtension:]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionRegistry extensionForDescriptor:fieldNumber:]", + "coverage": 0 + }, + { + "name": "CopyKeyValue", + "coverage": 0 + }, + { + "name": "-[GPBExtensionRegistry addExtensions:]", + "coverage": 0 + }, + { + "name": "__38-[GPBExtensionRegistry addExtensions:]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "GPBUnknownFieldSet.m", + "coverage": 0.11974110032362459, + "type": "objc", + "functions": [ + { + "name": "checkNumber", + "coverage": 0 + }, + { + "name": "CopyWorker", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet dealloc]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[GPBUnknownFieldSet isEqual:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet hash]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet hasField:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet getField:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet countOfFields]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet sortedFields]", + "coverage": 0 + }, + { + "name": "__34-[GPBUnknownFieldSet sortedFields]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet writeToCodedOutputStream:]", + "coverage": 0 + }, + { + "name": "__47-[GPBUnknownFieldSet writeToCodedOutputStream:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet description]", + "coverage": 0 + }, + { + "name": "GPBUnknownFieldSetSerializedSize", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet serializedSize]", + "coverage": 0 + }, + { + "name": "GPBUnknownFieldSetWriteAsMessageSetTo", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet writeAsMessageSetTo:]", + "coverage": 0 + }, + { + "name": "GPBUnknownFieldSetSerializedSizeAsMessageSet", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet serializedSizeAsMessageSet]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet data]", + "coverage": 0 + }, + { + "name": "+[GPBUnknownFieldSet isFieldTag:]", + "coverage": 1 + }, + { + "name": "-[GPBUnknownFieldSet addField:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet mutableFieldForNumber:create:]", + "coverage": 0 + }, + { + "name": "GPBUnknownFieldSetMergeUnknownFields", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet mergeUnknownFields:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet mergeFromData:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet mergeVarintField:value:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet mergeFieldFrom:input:]", + "coverage": 0.6153846153846154 + }, + { + "name": "-[GPBUnknownFieldSet mergeMessageSetMessage:data:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet addUnknownMapEntry:value:]", + "coverage": 0 + }, + { + "name": "-[GPBUnknownFieldSet mergeFromCodedInputStream:]", + "coverage": 0.75 + }, + { + "name": "-[GPBUnknownFieldSet getTags:]", + "coverage": 0 + } + ] + }, + { + "name": "GPBUtilities.m", + "coverage": 0.15045674368619022, + "type": "objc", + "functions": [ + { + "name": "GPBEmptyNSData", + "coverage": 1 + }, + { + "name": "__GPBEmptyNSData_block_invoke", + "coverage": 1 + }, + { + "name": "GPBMessageDropUnknownFieldsRecursively", + "coverage": 0 + }, + { + "name": "__GPBMessageDropUnknownFieldsRecursively_block_invoke", + "coverage": 0 + }, + { + "name": "__GPBMessageDropUnknownFieldsRecursively_block_invoke.32", + "coverage": 0 + }, + { + "name": "__GPBMessageDropUnknownFieldsRecursively_block_invoke.37", + "coverage": 0 + }, + { + "name": "__GPBMessageDropUnknownFieldsRecursively_block_invoke.42", + "coverage": 0 + }, + { + "name": "__GPBMessageDropUnknownFieldsRecursively_block_invoke.47", + "coverage": 0 + }, + { + "name": "__GPBMessageDropUnknownFieldsRecursively_block_invoke.52", + "coverage": 0 + }, + { + "name": "GPBCheckRuntimeVersionSupport", + "coverage": 0.36363636363636365 + }, + { + "name": "GPBCheckRuntimeVersionInternal", + "coverage": 0 + }, + { + "name": "GPBMessageHasFieldNumberSet", + "coverage": 0 + }, + { + "name": "GPBMessageHasFieldSet", + "coverage": 0 + }, + { + "name": "GPBClearMessageField", + "coverage": 0 + }, + { + "name": "GPBGetHasIvar", + "coverage": 0.8235294117647058 + }, + { + "name": "GPBGetHasOneof", + "coverage": 0 + }, + { + "name": "GPBSetHasIvar", + "coverage": 0.7058823529411765 + }, + { + "name": "GPBMaybeClearOneof", + "coverage": 0 + }, + { + "name": "GPBSetAutocreatedRetainedObjectIvarWithField", + "coverage": 1 + }, + { + "name": "GPBClearAutocreatedMessageIvarWithField", + "coverage": 0.8181818181818182 + }, + { + "name": "GPBSetObjectIvarWithField", + "coverage": 0 + }, + { + "name": "GPBSetCopyObjectIvarWithField", + "coverage": 0 + }, + { + "name": "GPBSetObjectIvarWithFieldInternal", + "coverage": 1 + }, + { + "name": "GPBSetRetainedObjectIvarWithFieldInternal", + "coverage": 0.2892561983471074 + }, + { + "name": "GPBGetObjectIvarWithFieldNoAutocreate", + "coverage": 0.75 + }, + { + "name": "GPBGetMessageEnumField", + "coverage": 0 + }, + { + "name": "GPBGetEnumIvarWithFieldInternal", + "coverage": 0 + }, + { + "name": "GPBSetMessageEnumField", + "coverage": 0 + }, + { + "name": "GPBSetEnumIvarWithFieldInternal", + "coverage": 0.75 + }, + { + "name": "GPBGetMessageRawEnumField", + "coverage": 0 + }, + { + "name": "GPBSetMessageRawEnumField", + "coverage": 0 + }, + { + "name": "GPBGetMessageBoolField", + "coverage": 1 + }, + { + "name": "GPBSetMessageBoolField", + "coverage": 0 + }, + { + "name": "GPBSetBoolIvarWithFieldInternal", + "coverage": 0.9285714285714286 + }, + { + "name": "GPBGetMessageInt32Field", + "coverage": 0.8823529411764706 + }, + { + "name": "GPBSetMessageInt32Field", + "coverage": 0 + }, + { + "name": "GPBSetInt32IvarWithFieldInternal", + "coverage": 0.896551724137931 + }, + { + "name": "GPBGetMessageUInt32Field", + "coverage": 0 + }, + { + "name": "GPBSetMessageUInt32Field", + "coverage": 0 + }, + { + "name": "GPBSetUInt32IvarWithFieldInternal", + "coverage": 0 + }, + { + "name": "GPBGetMessageInt64Field", + "coverage": 1 + }, + { + "name": "GPBSetMessageInt64Field", + "coverage": 0 + }, + { + "name": "GPBSetInt64IvarWithFieldInternal", + "coverage": 0.896551724137931 + }, + { + "name": "GPBGetMessageUInt64Field", + "coverage": 0 + }, + { + "name": "GPBSetMessageUInt64Field", + "coverage": 0 + }, + { + "name": "GPBSetUInt64IvarWithFieldInternal", + "coverage": 0 + }, + { + "name": "GPBGetMessageFloatField", + "coverage": 0 + }, + { + "name": "GPBSetMessageFloatField", + "coverage": 0 + }, + { + "name": "GPBSetFloatIvarWithFieldInternal", + "coverage": 0 + }, + { + "name": "GPBGetMessageDoubleField", + "coverage": 0 + }, + { + "name": "GPBSetMessageDoubleField", + "coverage": 0 + }, + { + "name": "GPBSetDoubleIvarWithFieldInternal", + "coverage": 0 + }, + { + "name": "GPBGetMessageStringField", + "coverage": 0 + }, + { + "name": "GPBSetMessageStringField", + "coverage": 0 + }, + { + "name": "GPBGetMessageBytesField", + "coverage": 0 + }, + { + "name": "GPBSetMessageBytesField", + "coverage": 0 + }, + { + "name": "GPBGetMessageMessageField", + "coverage": 0 + }, + { + "name": "GPBSetMessageMessageField", + "coverage": 0 + }, + { + "name": "GPBGetMessageGroupField", + "coverage": 0 + }, + { + "name": "GPBSetMessageGroupField", + "coverage": 0 + }, + { + "name": "GPBSetMessageRepeatedField", + "coverage": 0 + }, + { + "name": "BaseDataType", + "coverage": 0.75 + }, + { + "name": "DataTypesEquivalent", + "coverage": 1 + }, + { + "name": "TypeToString", + "coverage": 0 + }, + { + "name": "GPBSetMessageMapField", + "coverage": 0 + }, + { + "name": "GPBMessageEncodingForSelector", + "coverage": 1 + }, + { + "name": "AppendStringEscaped", + "coverage": 0 + }, + { + "name": "AppendBufferAsString", + "coverage": 0 + }, + { + "name": "AppendTextFormatForMapMessageField", + "coverage": 0 + }, + { + "name": "__AppendTextFormatForMapMessageField_block_invoke", + "coverage": 0 + }, + { + "name": "__AppendTextFormatForMapMessageField_block_invoke.408", + "coverage": 0 + }, + { + "name": "AppendTextFormatForMessageField", + "coverage": 0 + }, + { + "name": "AppendTextFormatForMessageExtensionRange", + "coverage": 0 + }, + { + "name": "AppendTextFormatForMessage", + "coverage": 0 + }, + { + "name": "GPBTextFormatForMessage", + "coverage": 0 + }, + { + "name": "GPBTextFormatForUnknownFieldSet", + "coverage": 0 + }, + { + "name": "Definition at 2012:76", + "coverage": 0 + }, + { + "name": "ReadRawByteFromData", + "coverage": 0 + }, + { + "name": "ReadRawVarint32FromData", + "coverage": 0 + }, + { + "name": "GPBDecodeTextFormatName", + "coverage": 0 + }, + { + "name": "GPBClassHasSel", + "coverage": 0 + } + ] + }, + { + "name": "GPBRootObject.m", + "coverage": 0.19254658385093168, + "type": "objc", + "functions": [ + { + "name": "jenkins_one_at_a_time_hash", + "coverage": 0 + }, + { + "name": "GPBRootExtensionKeyRetain", + "coverage": 0 + }, + { + "name": "GPBRootExtensionKeyRelease", + "coverage": 0 + }, + { + "name": "GPBRootExtensionCopyKeyDescription", + "coverage": 0 + }, + { + "name": "GPBRootExtensionKeyEqual", + "coverage": 0 + }, + { + "name": "GPBRootExtensionKeyHash", + "coverage": 0 + }, + { + "name": "+[GPBRootObject initialize]", + "coverage": 1 + }, + { + "name": "+[GPBRootObject extensionRegistry]", + "coverage": 1 + }, + { + "name": "+[GPBRootObject globallyRegisterExtension:]", + "coverage": 0 + }, + { + "name": "ExtensionForName", + "coverage": 0 + }, + { + "name": "GPBResolveExtensionClassMethod", + "coverage": 0 + }, + { + "name": "__GPBResolveExtensionClassMethod_block_invoke", + "coverage": 0 + }, + { + "name": "+[GPBRootObject resolveClassMethod:]", + "coverage": 0 + } + ] + }, + { + "name": "GPBCodedOutputStream.m", + "coverage": 0.21133525456292027, + "type": "objc", + "functions": [ + { + "name": "GPBRefreshBuffer", + "coverage": 0 + }, + { + "name": "GPBWriteRawByte", + "coverage": 0.6666666666666666 + }, + { + "name": "GPBWriteRawVarint32", + "coverage": 1 + }, + { + "name": "GPBWriteRawVarint64", + "coverage": 1 + }, + { + "name": "GPBWriteInt32NoTag", + "coverage": 1 + }, + { + "name": "GPBWriteUInt32", + "coverage": 0 + }, + { + "name": "GPBWriteTagWithFormat", + "coverage": 1 + }, + { + "name": "GPBWriteRawLittleEndian32", + "coverage": 0 + }, + { + "name": "GPBWriteRawLittleEndian64", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream dealloc]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream initWithOutputStream:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream initWithData:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream initWithOutputStream:data:]", + "coverage": 1 + }, + { + "name": "+[GPBCodedOutputStream streamWithOutputStream:]", + "coverage": 0 + }, + { + "name": "+[GPBCodedOutputStream streamWithData:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeDoubleNoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeDouble:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeFloatNoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeFloat:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeUInt64NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeUInt64:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeInt64NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeInt64:value:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeInt32NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeInt32:value:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeFixed64NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeFixed64:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeFixed32NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeFixed32:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeBoolNoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeBool:value:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeStringNoTag:]", + "coverage": 0.7872340425531915 + }, + { + "name": "-[GPBCodedOutputStream writeString:value:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeGroupNoTag:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeGroup:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeUnknownGroupNoTag:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeUnknownGroup:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeMessageNoTag:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeMessage:value:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeBytesNoTag:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeBytes:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeUInt32NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeUInt32:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeEnumNoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeEnum:value:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeSFixed32NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSFixed32:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSFixed64NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSFixed64:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSInt32NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSInt32:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSInt64NoTag:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSInt64:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeDoubleArray:values:tag:]", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeDoubleArray:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeDoubleArray:values:tag:]_block_invoke.73", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeDoubleArray:values:tag:]_block_invoke.79", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeFloatArray:values:tag:]", + "coverage": 0 + }, + { + "name": "__51-[GPBCodedOutputStream writeFloatArray:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__51-[GPBCodedOutputStream writeFloatArray:values:tag:]_block_invoke.89", + "coverage": 0 + }, + { + "name": "__51-[GPBCodedOutputStream writeFloatArray:values:tag:]_block_invoke.95", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeUInt64Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeUInt64Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeUInt64Array:values:tag:]_block_invoke.105", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeUInt64Array:values:tag:]_block_invoke.111", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeInt64Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__51-[GPBCodedOutputStream writeInt64Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__51-[GPBCodedOutputStream writeInt64Array:values:tag:]_block_invoke.121", + "coverage": 0 + }, + { + "name": "__51-[GPBCodedOutputStream writeInt64Array:values:tag:]_block_invoke.127", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeInt32Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__51-[GPBCodedOutputStream writeInt32Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__51-[GPBCodedOutputStream writeInt32Array:values:tag:]_block_invoke.137", + "coverage": 0 + }, + { + "name": "__51-[GPBCodedOutputStream writeInt32Array:values:tag:]_block_invoke.143", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeUInt32Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeUInt32Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeUInt32Array:values:tag:]_block_invoke.153", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeUInt32Array:values:tag:]_block_invoke.159", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeFixed64Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__53-[GPBCodedOutputStream writeFixed64Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__53-[GPBCodedOutputStream writeFixed64Array:values:tag:]_block_invoke.168", + "coverage": 0 + }, + { + "name": "__53-[GPBCodedOutputStream writeFixed64Array:values:tag:]_block_invoke.174", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeFixed32Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__53-[GPBCodedOutputStream writeFixed32Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__53-[GPBCodedOutputStream writeFixed32Array:values:tag:]_block_invoke.183", + "coverage": 0 + }, + { + "name": "__53-[GPBCodedOutputStream writeFixed32Array:values:tag:]_block_invoke.189", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSInt32Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeSInt32Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeSInt32Array:values:tag:]_block_invoke.198", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeSInt32Array:values:tag:]_block_invoke.204", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSInt64Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeSInt64Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeSInt64Array:values:tag:]_block_invoke.213", + "coverage": 0 + }, + { + "name": "__52-[GPBCodedOutputStream writeSInt64Array:values:tag:]_block_invoke.219", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSFixed64Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__54-[GPBCodedOutputStream writeSFixed64Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__54-[GPBCodedOutputStream writeSFixed64Array:values:tag:]_block_invoke.228", + "coverage": 0 + }, + { + "name": "__54-[GPBCodedOutputStream writeSFixed64Array:values:tag:]_block_invoke.234", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeSFixed32Array:values:tag:]", + "coverage": 0 + }, + { + "name": "__54-[GPBCodedOutputStream writeSFixed32Array:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__54-[GPBCodedOutputStream writeSFixed32Array:values:tag:]_block_invoke.243", + "coverage": 0 + }, + { + "name": "__54-[GPBCodedOutputStream writeSFixed32Array:values:tag:]_block_invoke.249", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeBoolArray:values:tag:]", + "coverage": 0 + }, + { + "name": "__50-[GPBCodedOutputStream writeBoolArray:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__50-[GPBCodedOutputStream writeBoolArray:values:tag:]_block_invoke.259", + "coverage": 0 + }, + { + "name": "__50-[GPBCodedOutputStream writeBoolArray:values:tag:]_block_invoke.265", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeEnumArray:values:tag:]", + "coverage": 0 + }, + { + "name": "__50-[GPBCodedOutputStream writeEnumArray:values:tag:]_block_invoke", + "coverage": 0 + }, + { + "name": "__50-[GPBCodedOutputStream writeEnumArray:values:tag:]_block_invoke.276", + "coverage": 0 + }, + { + "name": "__50-[GPBCodedOutputStream writeEnumArray:values:tag:]_block_invoke.282", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeStringArray:values:]", + "coverage": 0.6 + }, + { + "name": "-[GPBCodedOutputStream writeMessageArray:values:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeBytesArray:values:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeGroupArray:values:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeUnknownGroupArray:values:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeMessageSetExtension:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeRawMessageSetExtension:value:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream flush]", + "coverage": 0.6 + }, + { + "name": "-[GPBCodedOutputStream writeRawByte:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeRawData:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeRawPtr:offset:length:]", + "coverage": 0.28205128205128205 + }, + { + "name": "-[GPBCodedOutputStream writeTag:format:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeRawVarint32:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedOutputStream writeRawVarintSizeTAs32:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeRawVarint64:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeRawLittleEndian32:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedOutputStream writeRawLittleEndian64:]", + "coverage": 0 + }, + { + "name": "GPBComputeDoubleSizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeFloatSizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeUInt64SizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeInt64SizeNoTag", + "coverage": 1 + }, + { + "name": "GPBComputeInt32SizeNoTag", + "coverage": 1 + }, + { + "name": "GPBComputeSizeTSizeAsInt32NoTag", + "coverage": 0 + }, + { + "name": "GPBComputeFixed64SizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeFixed32SizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeBoolSizeNoTag", + "coverage": 1 + }, + { + "name": "GPBComputeStringSizeNoTag", + "coverage": 1 + }, + { + "name": "GPBComputeGroupSizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeUnknownGroupSizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeMessageSizeNoTag", + "coverage": 1 + }, + { + "name": "GPBComputeBytesSizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeUInt32SizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeEnumSizeNoTag", + "coverage": 1 + }, + { + "name": "GPBComputeSFixed32SizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeSFixed64SizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeSInt32SizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeSInt64SizeNoTag", + "coverage": 0 + }, + { + "name": "GPBComputeDoubleSize", + "coverage": 0 + }, + { + "name": "GPBComputeFloatSize", + "coverage": 0 + }, + { + "name": "GPBComputeUInt64Size", + "coverage": 0 + }, + { + "name": "GPBComputeInt64Size", + "coverage": 1 + }, + { + "name": "GPBComputeInt32Size", + "coverage": 1 + }, + { + "name": "GPBComputeFixed64Size", + "coverage": 0 + }, + { + "name": "GPBComputeFixed32Size", + "coverage": 0 + }, + { + "name": "GPBComputeBoolSize", + "coverage": 1 + }, + { + "name": "GPBComputeStringSize", + "coverage": 1 + }, + { + "name": "GPBComputeGroupSize", + "coverage": 0 + }, + { + "name": "GPBComputeUnknownGroupSize", + "coverage": 0 + }, + { + "name": "GPBComputeMessageSize", + "coverage": 0 + }, + { + "name": "GPBComputeBytesSize", + "coverage": 0 + }, + { + "name": "GPBComputeUInt32Size", + "coverage": 0 + }, + { + "name": "GPBComputeEnumSize", + "coverage": 1 + }, + { + "name": "GPBComputeSFixed32Size", + "coverage": 0 + }, + { + "name": "GPBComputeSFixed64Size", + "coverage": 0 + }, + { + "name": "GPBComputeSInt32Size", + "coverage": 0 + }, + { + "name": "GPBComputeSInt64Size", + "coverage": 0 + }, + { + "name": "GPBComputeMessageSetExtensionSize", + "coverage": 0 + }, + { + "name": "GPBComputeRawMessageSetExtensionSize", + "coverage": 0 + }, + { + "name": "GPBComputeTagSize", + "coverage": 1 + }, + { + "name": "GPBComputeWireFormatTagSize", + "coverage": 0 + }, + { + "name": "GPBComputeRawVarint32Size", + "coverage": 0.625 + }, + { + "name": "GPBComputeRawVarint32SizeForInteger", + "coverage": 1 + }, + { + "name": "GPBComputeRawVarint64Size", + "coverage": 0.5 + } + ] + }, + { + "name": "GPBDescriptor.m", + "coverage": 0.2474460839954597, + "type": "objc", + "functions": [ + { + "name": "SelFromStrings", + "coverage": 0.9375 + }, + { + "name": "NewFieldsArrayForHasIndex", + "coverage": 0 + }, + { + "name": "+[GPBDescriptor allocDescriptorForClass:rootClass:file:fields:fieldCount:storageSize:flags:]", + "coverage": 0.9487179487179487 + }, + { + "name": "-[GPBDescriptor initWithClass:file:fields:storageSize:wireFormat:]", + "coverage": 1 + }, + { + "name": "-[GPBDescriptor dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor setupOneofs:count:firstHasIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor setupExtraTextInfo:]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor setupExtensionRanges:count:]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor setupContainingMessageClassName:]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor setupMessageClassNameSuffix:]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor name]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor containingType]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor fullName]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor fieldWithNumber:]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor fieldWithName:]", + "coverage": 0 + }, + { + "name": "-[GPBDescriptor oneofWithName:]", + "coverage": 0 + }, + { + "name": "-[GPBFileDescriptor initWithPackage:objcPrefix:syntax:]", + "coverage": 1 + }, + { + "name": "-[GPBFileDescriptor initWithPackage:syntax:]", + "coverage": 1 + }, + { + "name": "-[GPBFileDescriptor dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBOneofDescriptor initWithName:fields:]", + "coverage": 0 + }, + { + "name": "-[GPBOneofDescriptor dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBOneofDescriptor name]", + "coverage": 0 + }, + { + "name": "-[GPBOneofDescriptor fieldWithNumber:]", + "coverage": 0 + }, + { + "name": "-[GPBOneofDescriptor fieldWithName:]", + "coverage": 0 + }, + { + "name": "GPBFieldTag", + "coverage": 0.8333333333333334 + }, + { + "name": "GPBFieldAlternateTag", + "coverage": 0 + }, + { + "name": "-[GPBFieldDescriptor init]", + "coverage": 0 + }, + { + "name": "-[GPBFieldDescriptor initWithFieldDescription:includesDefault:syntax:]", + "coverage": 0.7397260273972602 + }, + { + "name": "-[GPBFieldDescriptor dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBFieldDescriptor dataType]", + "coverage": 0 + }, + { + "name": "-[GPBFieldDescriptor hasDefaultValue]", + "coverage": 0 + }, + { + "name": "-[GPBFieldDescriptor number]", + "coverage": 0 + }, + { + "name": "-[GPBFieldDescriptor name]", + "coverage": 0 + }, + { + "name": "-[GPBFieldDescriptor isRequired]", + "coverage": 1 + }, + { + "name": "-[GPBFieldDescriptor isOptional]", + "coverage": 1 + }, + { + "name": "-[GPBFieldDescriptor fieldType]", + "coverage": 0.9 + }, + { + "name": "-[GPBFieldDescriptor mapKeyDataType]", + "coverage": 0 + }, + { + "name": "-[GPBFieldDescriptor isPackable]", + "coverage": 1 + }, + { + "name": "-[GPBFieldDescriptor isValidEnumValue:]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[GPBFieldDescriptor enumDescriptor]", + "coverage": 0 + }, + { + "name": "-[GPBFieldDescriptor defaultValue]", + "coverage": 1 + }, + { + "name": "-[GPBFieldDescriptor textFormatName]", + "coverage": 0 + }, + { + "name": "+[GPBEnumDescriptor allocDescriptorForName:valueNames:values:count:enumVerifier:]", + "coverage": 1 + }, + { + "name": "+[GPBEnumDescriptor allocDescriptorForName:valueNames:values:count:enumVerifier:extraTextFormatInfo:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumDescriptor initWithName:valueNames:values:count:enumVerifier:]", + "coverage": 1 + }, + { + "name": "-[GPBEnumDescriptor dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBEnumDescriptor calcValueNameOffsets]", + "coverage": 0 + }, + { + "name": "-[GPBEnumDescriptor enumNameForValue:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumDescriptor getValue:forEnumName:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumDescriptor getValue:forEnumTextFormatName:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumDescriptor textFormatNameForValue:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumDescriptor enumNameCount]", + "coverage": 0 + }, + { + "name": "-[GPBEnumDescriptor getEnumNameForIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBEnumDescriptor getEnumTextFormatNameForIndex:]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor initWithExtensionDescription:]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor dealloc]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor singletonName]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor singletonNameC]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor fieldNumber]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor dataType]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor wireType]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor alternateWireType]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor isRepeated]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor isPackable]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor msgClass]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor enumDescriptor]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor defaultValue]", + "coverage": 0 + }, + { + "name": "-[GPBExtensionDescriptor compareByFieldNumber:]", + "coverage": 0 + } + ] + }, + { + "name": "GPBMessage.m", + "coverage": 0.3391755923401493, + "type": "objc", + "functions": [ + { + "name": "MessageError", + "coverage": 0 + }, + { + "name": "ErrorFromException", + "coverage": 0.5238095238095238 + }, + { + "name": "CheckExtension", + "coverage": 0 + }, + { + "name": "CloneExtensionMap", + "coverage": 0 + }, + { + "name": "CreateArrayForField", + "coverage": 0.4426229508196721 + }, + { + "name": "CreateMapForField", + "coverage": 0 + }, + { + "name": "GetOrCreateArrayIvarWithField", + "coverage": 1 + }, + { + "name": "GetArrayIvarWithField", + "coverage": 1 + }, + { + "name": "GetOrCreateMapIvarWithField", + "coverage": 0 + }, + { + "name": "GetMapIvarWithField", + "coverage": 0 + }, + { + "name": "GPBCreateMessageWithAutocreator", + "coverage": 0 + }, + { + "name": "CreateMessageWithAutocreatorForExtension", + "coverage": 0 + }, + { + "name": "GPBWasMessageAutocreatedBy", + "coverage": 0 + }, + { + "name": "GPBBecomeVisibleToAutocreator", + "coverage": 0.375 + }, + { + "name": "GPBAutocreatedArrayModified", + "coverage": 0.8095238095238095 + }, + { + "name": "GPBAutocreatedDictionaryModified", + "coverage": 0 + }, + { + "name": "GPBClearMessageAutocreator", + "coverage": 0.13333333333333333 + }, + { + "name": "GPBPrepareReadOnlySemaphore", + "coverage": 0.875 + }, + { + "name": "GetOrMakeUnknownFields", + "coverage": 1 + }, + { + "name": "+[GPBMessage initialize]", + "coverage": 1 + }, + { + "name": "+[GPBMessage allocWithZone:]", + "coverage": 1 + }, + { + "name": "+[GPBMessage alloc]", + "coverage": 1 + }, + { + "name": "+[GPBMessage descriptor]", + "coverage": 1 + }, + { + "name": "+[GPBMessage message]", + "coverage": 0 + }, + { + "name": "-[GPBMessage init]", + "coverage": 1 + }, + { + "name": "-[GPBMessage initWithData:error:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage initWithData:extensionRegistry:error:]", + "coverage": 0.7777777777777778 + }, + { + "name": "-[GPBMessage initWithCodedInputStream:extensionRegistry:error:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage dealloc]", + "coverage": 1 + }, + { + "name": "-[GPBMessage copyFieldsInto:zone:descriptor:]", + "coverage": 0 + }, + { + "name": "__45-[GPBMessage copyFieldsInto:zone:descriptor:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBMessage copyWithZone:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage clear]", + "coverage": 0 + }, + { + "name": "-[GPBMessage internalClear:]", + "coverage": 0.620253164556962 + }, + { + "name": "-[GPBMessage isInitialized]", + "coverage": 0.6794871794871795 + }, + { + "name": "__27-[GPBMessage isInitialized]_block_invoke", + "coverage": 0 + }, + { + "name": "-[GPBMessage descriptor]", + "coverage": 1 + }, + { + "name": "-[GPBMessage data]", + "coverage": 1 + }, + { + "name": "-[GPBMessage delimitedData]", + "coverage": 0 + }, + { + "name": "-[GPBMessage writeToOutputStream:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage writeToCodedOutputStream:]", + "coverage": 0.7142857142857143 + }, + { + "name": "-[GPBMessage writeDelimitedToOutputStream:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage writeDelimitedToCodedOutputStream:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage writeField:toCodedOutputStream:]", + "coverage": 0.4308755760368664 + }, + { + "name": "-[GPBMessage getExtension:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage getExistingExtension:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage hasExtension:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage extensionsCurrentlySet]", + "coverage": 0 + }, + { + "name": "-[GPBMessage writeExtensionsToCodedOutputStream:range:sortedExtensions:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage setExtension:value:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage addExtension:value:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage setExtension:index:value:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage clearExtension:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage mergeFromData:extensionRegistry:]", + "coverage": 1 + }, + { + "name": "-[GPBMessage mergeDelimitedFromCodedInputStream:extensionRegistry:]", + "coverage": 0 + }, + { + "name": "+[GPBMessage parseFromData:error:]", + "coverage": 1 + }, + { + "name": "+[GPBMessage parseFromData:extensionRegistry:error:]", + "coverage": 1 + }, + { + "name": "+[GPBMessage parseFromCodedInputStream:extensionRegistry:error:]", + "coverage": 0 + }, + { + "name": "+[GPBMessage parseDelimitedFromCodedInputStream:extensionRegistry:error:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage unknownFields]", + "coverage": 0 + }, + { + "name": "-[GPBMessage setUnknownFields:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage parseMessageSet:extensionRegistry:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage parseUnknownField:extensionRegistry:tag:]", + "coverage": 0.38095238095238093 + }, + { + "name": "-[GPBMessage addUnknownMapEntry:value:]", + "coverage": 0 + }, + { + "name": "MergeSingleFieldFromCodedInputStream", + "coverage": 0.6075949367088608 + }, + { + "name": "MergeRepeatedPackedFieldFromCodedInputStream", + "coverage": 0 + }, + { + "name": "MergeRepeatedNotPackedFieldFromCodedInputStream", + "coverage": 0.746031746031746 + }, + { + "name": "-[GPBMessage mergeFromCodedInputStream:extensionRegistry:]", + "coverage": 0.6804123711340206 + }, + { + "name": "-[GPBMessage mergeFrom:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage isEqual:]", + "coverage": 0.04032258064516129 + }, + { + "name": "-[GPBMessage hash]", + "coverage": 0 + }, + { + "name": "-[GPBMessage description]", + "coverage": 0 + }, + { + "name": "-[GPBMessage debugQuickLookObject]", + "coverage": 0 + }, + { + "name": "-[GPBMessage serializedSize]", + "coverage": 0.8441558441558441 + }, + { + "name": "Definition at 2945:108", + "coverage": 0 + }, + { + "name": "ResolveIvarGet", + "coverage": 1 + }, + { + "name": "Definition at 3044:64", + "coverage": 1 + }, + { + "name": "Definition at 3052:64", + "coverage": 1 + }, + { + "name": "ResolveIvarSet", + "coverage": 1 + }, + { + "name": "Definition at 3088:76", + "coverage": 1 + }, + { + "name": "Definition at 3096:74", + "coverage": 1 + }, + { + "name": "+[GPBMessage resolveInstanceMethod:]", + "coverage": 0.6448598130841121 + }, + { + "name": "__36+[GPBMessage resolveInstanceMethod:]_block_invoke", + "coverage": 1 + }, + { + "name": "__36+[GPBMessage resolveInstanceMethod:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__36+[GPBMessage resolveInstanceMethod:]_block_invoke_3", + "coverage": 0 + }, + { + "name": "__36+[GPBMessage resolveInstanceMethod:]_block_invoke_4", + "coverage": 1 + }, + { + "name": "__36+[GPBMessage resolveInstanceMethod:]_block_invoke_5", + "coverage": 0 + }, + { + "name": "__36+[GPBMessage resolveInstanceMethod:]_block_invoke_6", + "coverage": 1 + }, + { + "name": "__36+[GPBMessage resolveInstanceMethod:]_block_invoke_7", + "coverage": 0 + }, + { + "name": "+[GPBMessage resolveClassMethod:]", + "coverage": 0 + }, + { + "name": "+[GPBMessage supportsSecureCoding]", + "coverage": 0 + }, + { + "name": "-[GPBMessage initWithCoder:]", + "coverage": 0 + }, + { + "name": "-[GPBMessage encodeWithCoder:]", + "coverage": 0 + }, + { + "name": "+[GPBMessage accessInstanceVariablesDirectly]", + "coverage": 0 + }, + { + "name": "GPBGetMessageRepeatedField", + "coverage": 0 + }, + { + "name": "GPBGetMessageMapField", + "coverage": 0 + }, + { + "name": "GPBGetObjectIvarWithField", + "coverage": 0.48148148148148145 + } + ] + }, + { + "name": "GPBCodedInputStream.m", + "coverage": 0.39452054794520547, + "type": "objc", + "functions": [ + { + "name": "RaiseException", + "coverage": 1 + }, + { + "name": "CheckRecursionLimit", + "coverage": 0.6 + }, + { + "name": "CheckSize", + "coverage": 0.45454545454545453 + }, + { + "name": "ReadRawByte", + "coverage": 1 + }, + { + "name": "ReadRawLittleEndian32", + "coverage": 0 + }, + { + "name": "ReadRawLittleEndian64", + "coverage": 0 + }, + { + "name": "ReadRawVarint64", + "coverage": 0.9285714285714286 + }, + { + "name": "ReadRawVarint32", + "coverage": 1 + }, + { + "name": "SkipRawData", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadDouble", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadFloat", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadUInt64", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadUInt32", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadInt64", + "coverage": 1 + }, + { + "name": "GPBCodedInputStreamReadInt32", + "coverage": 1 + }, + { + "name": "GPBCodedInputStreamReadFixed64", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadFixed32", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadEnum", + "coverage": 1 + }, + { + "name": "GPBCodedInputStreamReadSFixed32", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadSFixed64", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadSInt32", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadSInt64", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadBool", + "coverage": 1 + }, + { + "name": "GPBCodedInputStreamReadTag", + "coverage": 0.8421052631578947 + }, + { + "name": "GPBCodedInputStreamReadRetainedString", + "coverage": 0.6818181818181818 + }, + { + "name": "GPBCodedInputStreamReadRetainedBytes", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamReadRetainedBytesNoCopy", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamPushLimit", + "coverage": 0.7777777777777778 + }, + { + "name": "GPBCodedInputStreamPopLimit", + "coverage": 1 + }, + { + "name": "GPBCodedInputStreamBytesUntilLimit", + "coverage": 0 + }, + { + "name": "GPBCodedInputStreamIsAtEnd", + "coverage": 1 + }, + { + "name": "GPBCodedInputStreamCheckLastTagWas", + "coverage": 0.6 + }, + { + "name": "+[GPBCodedInputStream streamWithData:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream initWithData:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedInputStream dealloc]", + "coverage": 1 + }, + { + "name": "-[GPBCodedInputStream readTag]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream checkLastTagWas:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedInputStream skipField:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream skipMessage]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream isAtEnd]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream position]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream pushLimit:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream popLimit:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readDouble]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readFloat]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readUInt64]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readInt64]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readInt32]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readFixed64]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readFixed32]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readBool]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readString]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readGroup:message:extensionRegistry:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readUnknownGroup:message:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedInputStream readMessage:extensionRegistry:]", + "coverage": 1 + }, + { + "name": "-[GPBCodedInputStream readMapEntry:extensionRegistry:field:parentMessage:]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readBytes]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readUInt32]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readEnum]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readSFixed32]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readSFixed64]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readSInt32]", + "coverage": 0 + }, + { + "name": "-[GPBCodedInputStream readSInt64]", + "coverage": 0 + } + ] + }, + { + "name": "GPBUtilities_PackagePrivate.h", + "coverage": 0.4166666666666667, + "type": "objc", + "functions": [ + { + "name": "Definition at 54:52", + "coverage": 0 + }, + { + "name": "Definition at 65:47", + "coverage": 0 + }, + { + "name": "Definition at 73:54", + "coverage": 0 + }, + { + "name": "Definition at 79:52", + "coverage": 0 + }, + { + "name": "Definition at 85:54", + "coverage": 0 + }, + { + "name": "Definition at 91:52", + "coverage": 0 + }, + { + "name": "Definition at 97:74", + "coverage": 1 + }, + { + "name": "Definition at 101:74", + "coverage": 1 + }, + { + "name": "Definition at 109:50", + "coverage": 0 + }, + { + "name": "Definition at 117:50", + "coverage": 0 + }, + { + "name": "Definition at 125:50", + "coverage": 0 + }, + { + "name": "Definition at 134:50", + "coverage": 0 + }, + { + "name": "Definition at 143:55", + "coverage": 1 + }, + { + "name": "Definition at 155:56", + "coverage": 1 + }, + { + "name": "Definition at 165:70", + "coverage": 1 + }, + { + "name": "Definition at 169:69", + "coverage": 1 + }, + { + "name": "Definition at 173:68", + "coverage": 0 + }, + { + "name": "Definition at 178:65", + "coverage": 0 + }, + { + "name": "Definition at 192:65", + "coverage": 1 + }, + { + "name": "Definition at 197:48", + "coverage": 1 + } + ] + }, + { + "name": "GPBDescriptor_PackagePrivate.h", + "coverage": 0.64, + "type": "objc", + "functions": [ + { + "name": "Definition at 262:65", + "coverage": 1 + }, + { + "name": "Definition at 267:71", + "coverage": 1 + }, + { + "name": "Definition at 271:64", + "coverage": 1 + }, + { + "name": "Definition at 275:63", + "coverage": 1 + }, + { + "name": "Definition at 289:76", + "coverage": 1 + }, + { + "name": "Definition at 293:78", + "coverage": 0 + }, + { + "name": "Definition at 297:76", + "coverage": 0 + }, + { + "name": "Definition at 301:80", + "coverage": 0 + } + ] + }, + { + "name": "GPBWireFormat.m", + "coverage": 0.9523809523809523, + "type": "objc", + "functions": [ + { + "name": "GPBWireFormatMakeTag", + "coverage": 1 + }, + { + "name": "GPBWireFormatGetTagWireType", + "coverage": 1 + }, + { + "name": "GPBWireFormatGetTagFieldNumber", + "coverage": 1 + }, + { + "name": "GPBWireFormatIsValidTag", + "coverage": 1 + }, + { + "name": "GPBWireFormatForType", + "coverage": 0.9259259259259259 + } + ] + } + ] + }, + { + "name": "Storage_Example_iOS.app", + "coverage": 0.6872727272727273, + "files": [ + { + "name": "FIRStorageDownloadTask.m", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageDownloadTask initWithReference:fetcherService:dispatchQueue:file:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageDownloadTask dealloc]", + "coverage": 0 + }, + { + "name": "-[FIRStorageDownloadTask enqueue]", + "coverage": 0 + }, + { + "name": "-[FIRStorageDownloadTask enqueueWithData:]", + "coverage": 0 + }, + { + "name": "__42-[FIRStorageDownloadTask enqueueWithData:]_block_invoke", + "coverage": 0 + }, + { + "name": "__42-[FIRStorageDownloadTask enqueueWithData:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__42-[FIRStorageDownloadTask enqueueWithData:]_block_invoke.58", + "coverage": 0 + }, + { + "name": "__42-[FIRStorageDownloadTask enqueueWithData:]_block_invoke.75", + "coverage": 0 + }, + { + "name": "__42-[FIRStorageDownloadTask enqueueWithData:]_block_invoke.88", + "coverage": 0 + }, + { + "name": "__42-[FIRStorageDownloadTask enqueueWithData:]_block_invoke.100", + "coverage": 0 + }, + { + "name": "-[FIRStorageDownloadTask cancel]", + "coverage": 0 + }, + { + "name": "-[FIRStorageDownloadTask cancelWithError:]", + "coverage": 0 + }, + { + "name": "__42-[FIRStorageDownloadTask cancelWithError:]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRStorageDownloadTask pause]", + "coverage": 0 + }, + { + "name": "__31-[FIRStorageDownloadTask pause]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRStorageDownloadTask resume]", + "coverage": 0 + }, + { + "name": "__32-[FIRStorageDownloadTask resume]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "FIRStorageGetDownloadURLTask.m", + "coverage": 0.1569767441860465, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageGetDownloadURLTask initWithReference:fetcherService:dispatchQueue:completion:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageGetDownloadURLTask dealloc]", + "coverage": 0 + }, + { + "name": "+[FIRStorageGetDownloadURLTask downloadURLFromMetadataDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageGetDownloadURLTask enqueue]", + "coverage": 0 + }, + { + "name": "__39-[FIRStorageGetDownloadURLTask enqueue]_block_invoke", + "coverage": 0 + }, + { + "name": "__39-[FIRStorageGetDownloadURLTask enqueue]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__39-[FIRStorageGetDownloadURLTask enqueue]_block_invoke.91", + "coverage": 0 + } + ] + }, + { + "name": "FIRAppDelegate.m", + "coverage": 0.27586206896551724, + "type": "objc", + "functions": [ + { + "name": "-[FIRAppDelegate application:didFinishLaunchingWithOptions:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillResignActive:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidEnterBackground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationWillEnterForeground:]", + "coverage": 0 + }, + { + "name": "-[FIRAppDelegate applicationDidBecomeActive:]", + "coverage": 1 + }, + { + "name": "-[FIRAppDelegate applicationWillTerminate:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRStorageTaskSnapshot.m", + "coverage": 0.29310344827586204, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageTaskSnapshot initWithTask:state:metadata:reference:progress:error:]", + "coverage": 0.40476190476190477 + }, + { + "name": "-[FIRStorageTaskSnapshot description]", + "coverage": 0 + } + ] + }, + { + "name": "FIRStorageReference.m", + "coverage": 0.3220338983050847, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageReference init]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference initWithStorage:path:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference isEqual:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRStorageReference isEqualToFIRStorageReference:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference hash]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference description]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference stringValue]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference bucket]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference fullPath]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference name]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference root]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference parent]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference child:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference putData:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference putData:metadata:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference putData:metadata:completion:]", + "coverage": 0 + }, + { + "name": "__51-[FIRStorageReference putData:metadata:completion:]_block_invoke", + "coverage": 0 + }, + { + "name": "__51-[FIRStorageReference putData:metadata:completion:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__51-[FIRStorageReference putData:metadata:completion:]_block_invoke.75", + "coverage": 0 + }, + { + "name": "__51-[FIRStorageReference putData:metadata:completion:]_block_invoke_2.76", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference putFile:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference putFile:metadata:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference putFile:metadata:completion:]", + "coverage": 0.9444444444444444 + }, + { + "name": "__51-[FIRStorageReference putFile:metadata:completion:]_block_invoke", + "coverage": 0 + }, + { + "name": "__51-[FIRStorageReference putFile:metadata:completion:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__51-[FIRStorageReference putFile:metadata:completion:]_block_invoke.97", + "coverage": 1 + }, + { + "name": "__51-[FIRStorageReference putFile:metadata:completion:]_block_invoke_2.98", + "coverage": 1 + }, + { + "name": "-[FIRStorageReference dataWithMaxSize:completion:]", + "coverage": 0 + }, + { + "name": "__50-[FIRStorageReference dataWithMaxSize:completion:]_block_invoke", + "coverage": 0 + }, + { + "name": "__50-[FIRStorageReference dataWithMaxSize:completion:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__50-[FIRStorageReference dataWithMaxSize:completion:]_block_invoke.118", + "coverage": 0 + }, + { + "name": "__50-[FIRStorageReference dataWithMaxSize:completion:]_block_invoke_2.119", + "coverage": 0 + }, + { + "name": "__50-[FIRStorageReference dataWithMaxSize:completion:]_block_invoke.126", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference writeToFile:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference writeToFile:completion:]", + "coverage": 0 + }, + { + "name": "__46-[FIRStorageReference writeToFile:completion:]_block_invoke", + "coverage": 0 + }, + { + "name": "__46-[FIRStorageReference writeToFile:completion:]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__46-[FIRStorageReference writeToFile:completion:]_block_invoke.157", + "coverage": 0 + }, + { + "name": "__46-[FIRStorageReference writeToFile:completion:]_block_invoke_2.158", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference downloadURLWithCompletion:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference metadataWithCompletion:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference updateMetadata:completion:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageReference deleteWithCompletion:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRStorageUploadTask.m", + "coverage": 0.42782152230971127, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageUploadTask initWithReference:fetcherService:dispatchQueue:data:metadata:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageUploadTask initWithReference:fetcherService:dispatchQueue:file:metadata:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageUploadTask dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRStorageUploadTask enqueue]", + "coverage": 1 + }, + { + "name": "__31-[FIRStorageUploadTask enqueue]_block_invoke", + "coverage": 0.102803738317757 + }, + { + "name": "__31-[FIRStorageUploadTask enqueue]_block_invoke_2", + "coverage": 0 + }, + { + "name": "__31-[FIRStorageUploadTask enqueue]_block_invoke.130", + "coverage": 0 + }, + { + "name": "__31-[FIRStorageUploadTask enqueue]_block_invoke.149", + "coverage": 0 + }, + { + "name": "-[FIRStorageUploadTask finishTaskWithStatus:snapshot:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageUploadTask isContentToUploadValid:]", + "coverage": 0.782608695652174 + }, + { + "name": "-[FIRStorageUploadTask cancel]", + "coverage": 0 + }, + { + "name": "__30-[FIRStorageUploadTask cancel]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRStorageUploadTask pause]", + "coverage": 0 + }, + { + "name": "__29-[FIRStorageUploadTask pause]_block_invoke", + "coverage": 0 + }, + { + "name": "-[FIRStorageUploadTask resume]", + "coverage": 0 + }, + { + "name": "__30-[FIRStorageUploadTask resume]_block_invoke", + "coverage": 0 + } + ] + }, + { + "name": "FIRViewController.m", + "coverage": 0.5, + "type": "objc", + "functions": [ + { + "name": "-[FIRViewController viewDidLoad]", + "coverage": 1 + }, + { + "name": "-[FIRViewController didReceiveMemoryWarning]", + "coverage": 0 + } + ] + }, + { + "name": "FIRStorageObservableTask.m", + "coverage": 0.622093023255814, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageObservableTask initWithReference:fetcherService:dispatchQueue:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageObservableTask observeStatus:handler:]", + "coverage": 0.5441176470588235 + }, + { + "name": "-[FIRStorageObservableTask removeObserverWithHandle:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageObservableTask removeAllObserversForStatus:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageObservableTask removeAllObservers]", + "coverage": 1 + }, + { + "name": "-[FIRStorageObservableTask handlerDictionaryForStatus:]", + "coverage": 0.6923076923076923 + }, + { + "name": "-[FIRStorageObservableTask removeHandlersFromStatusMapForDictionary:]", + "coverage": 0 + }, + { + "name": "-[FIRStorageObservableTask fireHandlersForStatus:snapshot:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageObservableTask fireHandlers:snapshot:]", + "coverage": 0.8947368421052632 + }, + { + "name": "__50-[FIRStorageObservableTask fireHandlers:snapshot:]_block_invoke", + "coverage": 1 + }, + { + "name": "__50-[FIRStorageObservableTask fireHandlers:snapshot:]_block_invoke_2", + "coverage": 1 + } + ] + }, + { + "name": "FIRStorageErrors.m", + "coverage": 0.6981132075471698, + "type": "objc", + "functions": [ + { + "name": "+[FIRStorageErrors errorWithCode:]", + "coverage": 0 + }, + { + "name": "+[FIRStorageErrors errorWithCode:infoDictionary:]", + "coverage": 0.6222222222222222 + }, + { + "name": "+[FIRStorageErrors errorWithServerError:reference:]", + "coverage": 0.8823529411764706 + }, + { + "name": "+[FIRStorageErrors errorWithInvalidRequest:]", + "coverage": 1 + }, + { + "name": "+[FIRStorageErrors errorWithCustomMessage:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRDependency.m", + "coverage": 0.7857142857142857, + "type": "objc", + "functions": [ + { + "name": "+[FIRDependency dependencyWithProtocol:]", + "coverage": 0 + }, + { + "name": "+[FIRDependency dependencyWithProtocol:isRequired:]", + "coverage": 1 + }, + { + "name": "-[FIRDependency initWithProtocol:isRequired:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRBundleUtil.m", + "coverage": 0.7872340425531915, + "type": "objc", + "functions": [ + { + "name": "+[FIRBundleUtil relevantBundles]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil optionsDictionaryPathWithResourceName:andFileType:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil relevantURLSchemes]", + "coverage": 0 + }, + { + "name": "+[FIRBundleUtil hasBundleIdentifierPrefix:inBundles:]", + "coverage": 1 + }, + { + "name": "+[FIRBundleUtil bundleIdentifierByRemovingLastPartFrom:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRConfiguration.m", + "coverage": 0.8076923076923077, + "type": "objc", + "functions": [ + { + "name": "+[FIRConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__34+[FIRConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration init]", + "coverage": 1 + }, + { + "name": "-[FIRConfiguration setLoggerLevel:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRLogger.m", + "coverage": 0.8118811881188119, + "type": "objc", + "functions": [ + { + "name": "FIRLoggerInitializeASL", + "coverage": 1 + }, + { + "name": "__FIRLoggerInitializeASL_block_invoke", + "coverage": 0.9333333333333333 + }, + { + "name": "FIRSetAnalyticsDebugMode", + "coverage": 0 + }, + { + "name": "FIRSetLoggerLevel", + "coverage": 0 + }, + { + "name": "FIRResetLogger", + "coverage": 1 + }, + { + "name": "FIRSetLoggerUserDefaults", + "coverage": 1 + }, + { + "name": "FIRIsLoggableLevel", + "coverage": 0 + }, + { + "name": "FIRLogBasic", + "coverage": 1 + }, + { + "name": "Definition at 158:95", + "coverage": 1 + }, + { + "name": "+[FIRLoggerWrapper logWithLevel:withService:withCode:withMessage:withArgs:]", + "coverage": 0 + } + ] + }, + { + "name": "FIRStorage.m", + "coverage": 0.8274111675126904, + "type": "objc", + "functions": [ + { + "name": "+[FIRStorage initialize]", + "coverage": 1 + }, + { + "name": "__24+[FIRStorage initialize]_block_invoke", + "coverage": 1 + }, + { + "name": "__24+[FIRStorage initialize]_block_invoke_2", + "coverage": 0 + }, + { + "name": "+[FIRStorage fetcherServiceForApp:bucket:auth:]", + "coverage": 1 + }, + { + "name": "+[FIRStorage setGTMSessionFetcherLoggingEnabled:]", + "coverage": 0 + }, + { + "name": "+[FIRStorage storage]", + "coverage": 0 + }, + { + "name": "+[FIRStorage storageForApp:]", + "coverage": 1 + }, + { + "name": "+[FIRStorage storageWithURL:]", + "coverage": 0 + }, + { + "name": "+[FIRStorage storageForApp:URL:]", + "coverage": 1 + }, + { + "name": "-[FIRStorage initWithApp:bucket:auth:]", + "coverage": 1 + }, + { + "name": "-[FIRStorage copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIRStorage isEqual:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRStorage isEqualToFIRStorage:]", + "coverage": 1 + }, + { + "name": "-[FIRStorage hash]", + "coverage": 1 + }, + { + "name": "-[FIRStorage description]", + "coverage": 0 + }, + { + "name": "-[FIRStorage reference]", + "coverage": 1 + }, + { + "name": "-[FIRStorage referenceForURL:]", + "coverage": 1 + }, + { + "name": "-[FIRStorage referenceWithPath:]", + "coverage": 1 + }, + { + "name": "-[FIRStorage setCallbackQueue:]", + "coverage": 1 + }, + { + "name": "+[FIRStorage enableBackgroundTasks:]", + "coverage": 0 + }, + { + "name": "-[FIRStorage uploadTasks]", + "coverage": 0 + }, + { + "name": "-[FIRStorage downloadTasks]", + "coverage": 0 + } + ] + }, + { + "name": "FIRStorageTask.m", + "coverage": 0.8484848484848485, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageTask init]", + "coverage": 0 + }, + { + "name": "-[FIRStorageTask initWithReference:fetcherService:dispatchQueue:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageTask snapshot]", + "coverage": 1 + }, + { + "name": "-[FIRStorageTask dispatchAsync:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRApp.m", + "coverage": 0.8848, + "type": "objc", + "functions": [ + { + "name": "+[FIRApp configure]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithOptions:]", + "coverage": 1 + }, + { + "name": "+[FIRApp configureWithName:options:]", + "coverage": 0.9333333333333333 + }, + { + "name": "+[FIRApp defaultApp]", + "coverage": 1 + }, + { + "name": "+[FIRApp appNamed:]", + "coverage": 1 + }, + { + "name": "+[FIRApp allApps]", + "coverage": 1 + }, + { + "name": "+[FIRApp resetApps]", + "coverage": 1 + }, + { + "name": "-[FIRApp deleteApp:]", + "coverage": 0.7916666666666666 + }, + { + "name": "+[FIRApp addAppToAppDictionary:]", + "coverage": 0.6666666666666666 + }, + { + "name": "-[FIRApp initInstanceWithName:options:]", + "coverage": 1 + }, + { + "name": "-[FIRApp configureCore]", + "coverage": 0.7962962962962963 + }, + { + "name": "-[FIRApp options]", + "coverage": 1 + }, + { + "name": "-[FIRApp setDataCollectionDefaultEnabled:]", + "coverage": 0.9354838709677419 + }, + { + "name": "-[FIRApp isDataCollectionDefaultEnabled]", + "coverage": 1 + }, + { + "name": "+[FIRApp sendNotificationsToSDKs:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForMissingOptions]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForSubspecConfigurationFailureWithDomain:errorCode:service:reason:]", + "coverage": 1 + }, + { + "name": "+[FIRApp errorForInvalidAppID]", + "coverage": 0 + }, + { + "name": "+[FIRApp isDefaultAppConfigured]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerLibrary:withVersion:]", + "coverage": 1 + }, + { + "name": "+[FIRApp registerInternalLibrary:withName:withVersion:]", + "coverage": 1 + }, + { + "name": "__55+[FIRApp registerInternalLibrary:withName:withVersion:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRApp firebaseUserAgent]", + "coverage": 1 + }, + { + "name": "-[FIRApp checkExpectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp isAppIDValid]", + "coverage": 1 + }, + { + "name": "+[FIRApp validateAppID:]", + "coverage": 0.9473684210526315 + }, + { + "name": "+[FIRApp actualBundleID]", + "coverage": 0 + }, + { + "name": "+[FIRApp validateAppIDFormat:withVersion:]", + "coverage": 0.8450704225352113 + }, + { + "name": "+[FIRApp validateAppIDFingerprint:withVersion:]", + "coverage": 0.8620689655172413 + }, + { + "name": "-[FIRApp expectedBundleID]", + "coverage": 1 + }, + { + "name": "-[FIRApp clearDataCollectionSwitchFromUserDefaults]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromUserDefaultsForApp:]", + "coverage": 1 + }, + { + "name": "+[FIRApp readDataCollectionSwitchFromPlist]", + "coverage": 1 + }, + { + "name": "__43+[FIRApp readDataCollectionSwitchFromPlist]_block_invoke", + "coverage": 0.75 + }, + { + "name": "-[FIRApp sendLogsWithServiceName:version:error:]", + "coverage": 0.2631578947368421 + } + ] + }, + { + "name": "FIRStoragePath.m", + "coverage": 0.9285714285714286, + "type": "objc", + "functions": [ + { + "name": "+[FIRStoragePath pathFromString:]", + "coverage": 1 + }, + { + "name": "+[FIRStoragePath pathFromGSURI:]", + "coverage": 1 + }, + { + "name": "+[FIRStoragePath pathFromHTTPURL:]", + "coverage": 1 + }, + { + "name": "-[FIRStoragePath initWithBucket:object:]", + "coverage": 1 + }, + { + "name": "-[FIRStoragePath copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIRStoragePath isEqual:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRStoragePath isEqualToFIRStoragePath:]", + "coverage": 1 + }, + { + "name": "-[FIRStoragePath hash]", + "coverage": 0 + }, + { + "name": "-[FIRStoragePath description]", + "coverage": 0 + }, + { + "name": "-[FIRStoragePath stringValue]", + "coverage": 1 + }, + { + "name": "-[FIRStoragePath child:]", + "coverage": 1 + }, + { + "name": "-[FIRStoragePath parent]", + "coverage": 1 + }, + { + "name": "-[FIRStoragePath root]", + "coverage": 1 + }, + { + "name": "-[FIRStoragePath standardizedPathForString:]", + "coverage": 1 + }, + { + "name": "__44-[FIRStoragePath standardizedPathForString:]_block_invoke", + "coverage": 1 + } + ] + }, + { + "name": "FIROptions.m", + "coverage": 0.9489795918367347, + "type": "objc", + "functions": [ + { + "name": "+[FIROptions defaultOptions]", + "coverage": 0.8461538461538461 + }, + { + "name": "+[FIROptions initialize]", + "coverage": 1 + }, + { + "name": "+[FIROptions defaultOptionsDictionary]", + "coverage": 1 + }, + { + "name": "+[FIROptions plistFilePathWithName:]", + "coverage": 1 + }, + { + "name": "+[FIROptions resetDefaultOptions]", + "coverage": 1 + }, + { + "name": "-[FIROptions initInternalWithOptionsDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithContentsOfFile:]", + "coverage": 1 + }, + { + "name": "-[FIROptions initWithGoogleAppID:GCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions APIKey]", + "coverage": 1 + }, + { + "name": "-[FIROptions checkEditingLocked]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAPIKey:]", + "coverage": 1 + }, + { + "name": "-[FIROptions clientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions trackingID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setTrackingID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions GCMSenderID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGCMSenderID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions projectID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setProjectID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions androidClientID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setAndroidClientID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions googleAppID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setGoogleAppID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions libraryVersionID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setLibraryVersionID:]", + "coverage": 0 + }, + { + "name": "-[FIROptions databaseURL]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDatabaseURL:]", + "coverage": 1 + }, + { + "name": "-[FIROptions storageBucket]", + "coverage": 1 + }, + { + "name": "-[FIROptions setStorageBucket:]", + "coverage": 1 + }, + { + "name": "-[FIROptions setDeepLinkURLScheme:]", + "coverage": 1 + }, + { + "name": "-[FIROptions bundleID]", + "coverage": 1 + }, + { + "name": "-[FIROptions setBundleID:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionaryWithInfoDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIROptions analyticsOptionsDictionary]", + "coverage": 1 + }, + { + "name": "-[FIROptions isMeasurementEnabled]", + "coverage": 0.8181818181818182 + }, + { + "name": "-[FIROptions isAnalyticsCollectionExpicitlySet]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionEnabled]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsCollectionDeactivated]", + "coverage": 1 + }, + { + "name": "-[FIROptions isAnalyticsEnabled]", + "coverage": 0 + }, + { + "name": "-[FIROptions isSignInEnabled]", + "coverage": 0 + } + ] + }, + { + "name": "FIRAnalyticsConfiguration.m", + "coverage": 0.9545454545454546, + "type": "objc", + "functions": [ + { + "name": "+[FIRAnalyticsConfiguration sharedInstance]", + "coverage": 1 + }, + { + "name": "__43+[FIRAnalyticsConfiguration sharedInstance]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration postNotificationName:value:]", + "coverage": 0.75 + }, + { + "name": "-[FIRAnalyticsConfiguration setMinimumSessionInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setSessionTimeoutInterval:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:]", + "coverage": 1 + }, + { + "name": "-[FIRAnalyticsConfiguration setAnalyticsCollectionEnabled:persistSetting:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRStorageMetadata.m", + "coverage": 0.9664804469273743, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageMetadata init]", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata initWithDictionary:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata copyWithZone:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata isEqual:]", + "coverage": 0.8333333333333334 + }, + { + "name": "-[FIRStorageMetadata isEqualToFIRStorageMetadata:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata hash]", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata description]", + "coverage": 0 + }, + { + "name": "-[FIRStorageMetadata dictionaryRepresentation]", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata isFile]", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata isFolder]", + "coverage": 1 + }, + { + "name": "+[FIRStorageMetadata removeMatchingMetadata:oldMetadata:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata updatedMetadata]", + "coverage": 1 + }, + { + "name": "setupDateFormatterOnce", + "coverage": 1 + }, + { + "name": "__setupDateFormatterOnce_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata dateFromRFC3339String:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageMetadata RFC3339StringFromDate:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRStorageUtils.m", + "coverage": 0.9746835443037974, + "type": "objc", + "functions": [ + { + "name": "+[FIRStorageUtils GCSEscapedString:]", + "coverage": 1 + }, + { + "name": "+[FIRStorageUtils MIMETypeForExtension:]", + "coverage": 0.8823529411764706 + }, + { + "name": "+[FIRStorageUtils queryStringForDictionary:]", + "coverage": 1 + }, + { + "name": "__44+[FIRStorageUtils queryStringForDictionary:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRStorageUtils defaultRequestForPath:]", + "coverage": 1 + }, + { + "name": "+[FIRStorageUtils encodedURLForPath:]", + "coverage": 1 + }, + { + "name": "+[NSDictionary(FIRStorageNSDictionaryJSONHelpers) frs_dictionaryFromJSONData:]", + "coverage": 1 + }, + { + "name": "+[NSData(FIRStorageNSDataJSONHelpers) frs_dataFromJSONDictionary:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRStorageDeleteTask.m", + "coverage": 0.9795918367346939, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageDeleteTask dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRStorageDeleteTask initWithReference:fetcherService:dispatchQueue:completion:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageDeleteTask enqueue]", + "coverage": 1 + }, + { + "name": "__31-[FIRStorageDeleteTask enqueue]_block_invoke", + "coverage": 0.9444444444444444 + }, + { + "name": "__31-[FIRStorageDeleteTask enqueue]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__31-[FIRStorageDeleteTask enqueue]_block_invoke.36", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentContainer.m", + "coverage": 0.9818181818181818, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:]", + "coverage": 1 + }, + { + "name": "__55+[FIRComponentContainer registerAsComponentRegistrant:]_block_invoke", + "coverage": 1 + }, + { + "name": "+[FIRComponentContainer registerAsComponentRegistrant:inSet:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer initWithApp:registrants:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer populateComponentsFromRegisteredClasses:forApp:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer instantiateInstanceForProtocol:withBlock:]", + "coverage": 0.9285714285714286 + }, + { + "name": "-[FIRComponentContainer instanceForProtocol:]", + "coverage": 1 + }, + { + "name": "-[FIRComponentContainer removeAllCachedInstances]", + "coverage": 1 + } + ] + }, + { + "name": "FIRStorageGetMetadataTask.m", + "coverage": 0.9848484848484849, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageGetMetadataTask initWithReference:fetcherService:dispatchQueue:completion:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageGetMetadataTask dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRStorageGetMetadataTask enqueue]", + "coverage": 1 + }, + { + "name": "__36-[FIRStorageGetMetadataTask enqueue]_block_invoke", + "coverage": 0.9574468085106383 + }, + { + "name": "__36-[FIRStorageGetMetadataTask enqueue]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__36-[FIRStorageGetMetadataTask enqueue]_block_invoke.48", + "coverage": 1 + } + ] + }, + { + "name": "FIRStorageUpdateMetadataTask.m", + "coverage": 0.9865771812080537, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageUpdateMetadataTask initWithReference:fetcherService:dispatchQueue:metadata:completion:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageUpdateMetadataTask dealloc]", + "coverage": 1 + }, + { + "name": "-[FIRStorageUpdateMetadataTask enqueue]", + "coverage": 1 + }, + { + "name": "__39-[FIRStorageUpdateMetadataTask enqueue]_block_invoke", + "coverage": 0.9636363636363636 + }, + { + "name": "__39-[FIRStorageUpdateMetadataTask enqueue]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__39-[FIRStorageUpdateMetadataTask enqueue]_block_invoke.70", + "coverage": 1 + } + ] + }, + { + "name": "FIRStorageTokenAuthorizer.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageTokenAuthorizer initWithGoogleAppID:fetcherService:authProvider:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageTokenAuthorizer authorizeRequest:delegate:didFinishSelector:]", + "coverage": 1 + }, + { + "name": "__73-[FIRStorageTokenAuthorizer authorizeRequest:delegate:didFinishSelector:]_block_invoke", + "coverage": 1 + }, + { + "name": "__73-[FIRStorageTokenAuthorizer authorizeRequest:delegate:didFinishSelector:]_block_invoke_2", + "coverage": 1 + }, + { + "name": "__73-[FIRStorageTokenAuthorizer authorizeRequest:delegate:didFinishSelector:]_block_invoke.54", + "coverage": 1 + }, + { + "name": "-[FIRStorageTokenAuthorizer stopAuthorization]", + "coverage": 1 + }, + { + "name": "-[FIRStorageTokenAuthorizer stopAuthorizationForRequest:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageTokenAuthorizer isAuthorizingRequest:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageTokenAuthorizer isAuthorizedRequest:]", + "coverage": 1 + }, + { + "name": "-[FIRStorageTokenAuthorizer userEmail]", + "coverage": 1 + } + ] + }, + { + "name": "FIRAppAssociationRegistration.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRAppAssociationRegistration registeredObjectWithHost:key:creationBlock:]", + "coverage": 1 + } + ] + }, + { + "name": "main.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "main", + "coverage": 1 + } + ] + }, + { + "name": "FIRStorageComponent.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "-[FIRStorageComponent initWithApp:]", + "coverage": 1 + }, + { + "name": "+[FIRStorageComponent load]", + "coverage": 1 + }, + { + "name": "+[FIRStorageComponent componentsToRegister]", + "coverage": 1 + }, + { + "name": "__43+[FIRStorageComponent componentsToRegister]_block_invoke", + "coverage": 1 + }, + { + "name": "-[FIRStorageComponent storageForBucket:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponentType.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponentType instanceForProtocol:inContainer:]", + "coverage": 1 + } + ] + }, + { + "name": "FIRComponent.m", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "+[FIRComponent componentWithProtocol:creationBlock:]", + "coverage": 1 + }, + { + "name": "+[FIRComponent componentWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + }, + { + "name": "-[FIRComponent initWithProtocol:instantiationTiming:dependencies:creationBlock:]", + "coverage": 1 + } + ] + } + ] + }, + { + "name": "leveldb.framework", + "coverage": 0.5077523967333413, + "files": [ + { + "name": "bloom.cc", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::(anonymous namespace)::BloomHash(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::BloomFilterPolicy::BloomFilterPolicy(int)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::BloomFilterPolicy::Name() const", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::BloomFilterPolicy::CreateFilter(leveldb::Slice const*, int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::BloomFilterPolicy::KeyMayMatch(leveldb::Slice const&, leveldb::Slice const&) const", + "coverage": 0 + }, + { + "name": "leveldb::NewBloomFilterPolicy(int)", + "coverage": 0 + } + ] + }, + { + "name": "c.cc", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb_comparator_t::~leveldb_comparator_t()", + "coverage": 0 + }, + { + "name": "leveldb_comparator_t::Compare(leveldb::Slice const&, leveldb::Slice const&) const", + "coverage": 0 + }, + { + "name": "leveldb_comparator_t::Name() const", + "coverage": 0 + }, + { + "name": "leveldb_comparator_t::FindShortestSeparator(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, leveldb::Slice const&) const", + "coverage": 0 + }, + { + "name": "leveldb_comparator_t::FindShortSuccessor(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_t::~leveldb_filterpolicy_t()", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_t::Name() const", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_t::CreateFilter(leveldb::Slice const*, int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_t::KeyMayMatch(leveldb::Slice const&, leveldb::Slice const&) const", + "coverage": 0 + }, + { + "name": "SaveError(char**, leveldb::Status const&)", + "coverage": 0 + }, + { + "name": "CopyString(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + }, + { + "name": "leveldb_open", + "coverage": 0 + }, + { + "name": "leveldb_close", + "coverage": 0 + }, + { + "name": "leveldb_put", + "coverage": 0 + }, + { + "name": "leveldb_delete", + "coverage": 0 + }, + { + "name": "leveldb_write", + "coverage": 0 + }, + { + "name": "leveldb_get", + "coverage": 0 + }, + { + "name": "leveldb_create_iterator", + "coverage": 0 + }, + { + "name": "leveldb_create_snapshot", + "coverage": 0 + }, + { + "name": "leveldb_release_snapshot", + "coverage": 0 + }, + { + "name": "leveldb_property_value", + "coverage": 0 + }, + { + "name": "leveldb_approximate_sizes", + "coverage": 0 + }, + { + "name": "leveldb_compact_range", + "coverage": 0 + }, + { + "name": "leveldb_destroy_db", + "coverage": 0 + }, + { + "name": "leveldb_repair_db", + "coverage": 0 + }, + { + "name": "leveldb_iter_destroy", + "coverage": 0 + }, + { + "name": "leveldb_iter_valid", + "coverage": 0 + }, + { + "name": "leveldb_iter_seek_to_first", + "coverage": 0 + }, + { + "name": "leveldb_iter_seek_to_last", + "coverage": 0 + }, + { + "name": "leveldb_iter_seek", + "coverage": 0 + }, + { + "name": "leveldb_iter_next", + "coverage": 0 + }, + { + "name": "leveldb_iter_prev", + "coverage": 0 + }, + { + "name": "leveldb_iter_key", + "coverage": 0 + }, + { + "name": "leveldb_iter_value", + "coverage": 0 + }, + { + "name": "leveldb_iter_get_error", + "coverage": 0 + }, + { + "name": "leveldb_writebatch_create", + "coverage": 0 + }, + { + "name": "leveldb_writebatch_destroy", + "coverage": 0 + }, + { + "name": "leveldb_writebatch_clear", + "coverage": 0 + }, + { + "name": "leveldb_writebatch_put", + "coverage": 0 + }, + { + "name": "leveldb_writebatch_delete", + "coverage": 0 + }, + { + "name": "leveldb_writebatch_iterate", + "coverage": 0 + }, + { + "name": "leveldb_writebatch_iterate::H::Put(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb_writebatch_iterate::H::Delete(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb_options_create", + "coverage": 0 + }, + { + "name": "leveldb_options_destroy", + "coverage": 0 + }, + { + "name": "leveldb_options_set_comparator", + "coverage": 0 + }, + { + "name": "leveldb_options_set_filter_policy", + "coverage": 0 + }, + { + "name": "leveldb_options_set_create_if_missing", + "coverage": 0 + }, + { + "name": "leveldb_options_set_error_if_exists", + "coverage": 0 + }, + { + "name": "leveldb_options_set_paranoid_checks", + "coverage": 0 + }, + { + "name": "leveldb_options_set_env", + "coverage": 0 + }, + { + "name": "leveldb_options_set_info_log", + "coverage": 0 + }, + { + "name": "leveldb_options_set_write_buffer_size", + "coverage": 0 + }, + { + "name": "leveldb_options_set_max_open_files", + "coverage": 0 + }, + { + "name": "leveldb_options_set_cache", + "coverage": 0 + }, + { + "name": "leveldb_options_set_block_size", + "coverage": 0 + }, + { + "name": "leveldb_options_set_block_restart_interval", + "coverage": 0 + }, + { + "name": "leveldb_options_set_compression", + "coverage": 0 + }, + { + "name": "leveldb_comparator_create", + "coverage": 0 + }, + { + "name": "leveldb_comparator_destroy", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_create", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_destroy", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_create_bloom", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_create_bloom::Wrapper::~Wrapper()", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_create_bloom::Wrapper::Name() const", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_create_bloom::Wrapper::CreateFilter(leveldb::Slice const*, int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_create_bloom::Wrapper::KeyMayMatch(leveldb::Slice const&, leveldb::Slice const&) const", + "coverage": 0 + }, + { + "name": "leveldb_filterpolicy_create_bloom::Wrapper::DoNothing(void*)", + "coverage": 0 + }, + { + "name": "leveldb_readoptions_create", + "coverage": 0 + }, + { + "name": "leveldb_readoptions_destroy", + "coverage": 0 + }, + { + "name": "leveldb_readoptions_set_verify_checksums", + "coverage": 0 + }, + { + "name": "leveldb_readoptions_set_fill_cache", + "coverage": 0 + }, + { + "name": "leveldb_readoptions_set_snapshot", + "coverage": 0 + }, + { + "name": "leveldb_writeoptions_create", + "coverage": 0 + }, + { + "name": "leveldb_writeoptions_destroy", + "coverage": 0 + }, + { + "name": "leveldb_writeoptions_set_sync", + "coverage": 0 + }, + { + "name": "leveldb_cache_create_lru", + "coverage": 0 + }, + { + "name": "leveldb_cache_destroy", + "coverage": 0 + }, + { + "name": "leveldb_create_default_env", + "coverage": 0 + }, + { + "name": "leveldb_env_destroy", + "coverage": 0 + }, + { + "name": "leveldb_free", + "coverage": 0 + }, + { + "name": "leveldb_major_version", + "coverage": 0 + }, + { + "name": "leveldb_minor_version", + "coverage": 0 + } + ] + }, + { + "name": "testutil.h", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::test::ErrorEnv::ErrorEnv()", + "coverage": 0 + }, + { + "name": "leveldb::test::ErrorEnv::NewWritableFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile**)", + "coverage": 0 + }, + { + "name": "leveldb::test::ErrorEnv::NewAppendableFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile**)", + "coverage": 0 + } + ] + }, + { + "name": "testutil.cc", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::test::RandomString(leveldb::Random*, int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 0 + }, + { + "name": "leveldb::test::RandomKey(leveldb::Random*, int)", + "coverage": 0 + }, + { + "name": "leveldb::test::CompressibleString(leveldb::Random*, double, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 0 + } + ] + }, + { + "name": "testharness.h", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::test::Tester::Tester(char const*, int)", + "coverage": 0 + }, + { + "name": "leveldb::test::Tester::~Tester()", + "coverage": 0 + }, + { + "name": "leveldb::test::Tester::Is(bool, char const*)", + "coverage": 0 + }, + { + "name": "leveldb::test::Tester::IsOk(leveldb::Status const&)", + "coverage": 0 + }, + { + "name": "leveldb::test::Tester& leveldb::test::Tester::operator<<<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + } + ] + }, + { + "name": "block.h", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::Block::size() const", + "coverage": 0 + } + ] + }, + { + "name": "testharness.cc", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::test::RegisterTest(char const*, char const*, void (*)())", + "coverage": 0 + }, + { + "name": "leveldb::test::RunAllTests()", + "coverage": 0 + }, + { + "name": "leveldb::test::TmpDir()", + "coverage": 0 + }, + { + "name": "leveldb::test::RandomSeed()", + "coverage": 0 + } + ] + }, + { + "name": "dumpfile.cc", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::(anonymous namespace)::GuessType(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::FileType*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::CorruptionReporter::Corruption(unsigned long, leveldb::Status const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PrintLogContents(leveldb::Env*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, void (*)(unsigned long long, leveldb::Slice, leveldb::WritableFile*), leveldb::WritableFile*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::WriteBatchItemPrinter::Put(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::WriteBatchItemPrinter::Delete(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::WriteBatchPrinter(unsigned long long, leveldb::Slice, leveldb::WritableFile*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::DumpLog(leveldb::Env*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::VersionEditPrinter(unsigned long long, leveldb::Slice, leveldb::WritableFile*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::DumpDescriptor(leveldb::Env*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::DumpTable(leveldb::Env*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile*)", + "coverage": 0 + }, + { + "name": "leveldb::DumpFile(leveldb::Env*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile*)", + "coverage": 0 + } + ] + }, + { + "name": "filter_block.cc", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::FilterBlockBuilder::FilterBlockBuilder(leveldb::FilterPolicy const*)", + "coverage": 0 + }, + { + "name": "leveldb::FilterBlockBuilder::StartBlock(unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::FilterBlockBuilder::AddKey(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::FilterBlockBuilder::Finish()", + "coverage": 0 + }, + { + "name": "leveldb::FilterBlockBuilder::GenerateFilter()", + "coverage": 0 + }, + { + "name": "leveldb::FilterBlockReader::FilterBlockReader(leveldb::FilterPolicy const*, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::FilterBlockReader::KeyMayMatch(unsigned long long, leveldb::Slice const&)", + "coverage": 0 + } + ] + }, + { + "name": "histogram.h", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::Histogram::Histogram()", + "coverage": 0 + }, + { + "name": "leveldb::Histogram::~Histogram()", + "coverage": 0 + } + ] + }, + { + "name": "histogram.cc", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::Histogram::Clear()", + "coverage": 0 + }, + { + "name": "leveldb::Histogram::Add(double)", + "coverage": 0 + }, + { + "name": "leveldb::Histogram::Merge(leveldb::Histogram const&)", + "coverage": 0 + }, + { + "name": "leveldb::Histogram::Median() const", + "coverage": 0 + }, + { + "name": "leveldb::Histogram::Percentile(double) const", + "coverage": 0 + }, + { + "name": "leveldb::Histogram::Average() const", + "coverage": 0 + }, + { + "name": "leveldb::Histogram::StandardDeviation() const", + "coverage": 0 + }, + { + "name": "leveldb::Histogram::ToString() const", + "coverage": 0 + } + ] + }, + { + "name": "repair.cc", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "leveldb::(anonymous namespace)::Repairer::Repairer(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::Options const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::~Repairer()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::Run()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::FindFiles()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::ConvertLogFilesToTables()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::ConvertLogToTable(unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::ConvertLogToTable(unsigned long long)::LogReporter::Corruption(unsigned long, leveldb::Status const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::ExtractMetaData()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::NewTableIterator(leveldb::FileMetaData const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::ScanTable(unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::RepairTable(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::(anonymous namespace)::Repairer::TableInfo)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::WriteDescriptor()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::Repairer::ArchiveFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + }, + { + "name": "leveldb::RepairDB(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::Options const&)", + "coverage": 0 + } + ] + }, + { + "name": "env.h", + "coverage": 0.10909090909090909, + "type": "objc", + "functions": [ + { + "name": "leveldb::Env::Env()", + "coverage": 1 + }, + { + "name": "leveldb::SequentialFile::SequentialFile()", + "coverage": 1 + }, + { + "name": "leveldb::RandomAccessFile::RandomAccessFile()", + "coverage": 1 + }, + { + "name": "leveldb::WritableFile::WritableFile()", + "coverage": 1 + }, + { + "name": "leveldb::Logger::Logger()", + "coverage": 1 + }, + { + "name": "leveldb::FileLock::FileLock()", + "coverage": 1 + }, + { + "name": "leveldb::EnvWrapper::EnvWrapper(leveldb::Env*)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::target() const", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::NewSequentialFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::SequentialFile**)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::NewRandomAccessFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::RandomAccessFile**)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::NewWritableFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile**)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::NewAppendableFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile**)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::FileExists(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::GetChildren(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >*)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::DeleteFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::CreateDir(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::DeleteDir(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::GetFileSize(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long*)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::RenameFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::LockFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::FileLock**)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::UnlockFile(leveldb::FileLock*)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::Schedule(void (*)(void*), void*)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::StartThread(void (*)(void*), void*)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::GetTestDirectory(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::NewLogger(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::Logger**)", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::NowMicros()", + "coverage": 0 + }, + { + "name": "leveldb::EnvWrapper::SleepForMicroseconds(int)", + "coverage": 0 + } + ] + }, + { + "name": "snapshot.h", + "coverage": 0.17391304347826086, + "type": "objc", + "functions": [ + { + "name": "leveldb::SnapshotList::SnapshotList()", + "coverage": 1 + }, + { + "name": "leveldb::SnapshotList::empty() const", + "coverage": 0 + }, + { + "name": "leveldb::SnapshotList::oldest() const", + "coverage": 0 + }, + { + "name": "leveldb::SnapshotList::newest() const", + "coverage": 0 + }, + { + "name": "leveldb::SnapshotList::New(unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::SnapshotList::Delete(leveldb::SnapshotImpl const*)", + "coverage": 0 + } + ] + }, + { + "name": "port_posix.h", + "coverage": 0.3076923076923077, + "type": "objc", + "functions": [ + { + "name": "leveldb::port::Mutex::AssertHeld()", + "coverage": 1 + }, + { + "name": "leveldb::port::Snappy_Compress(char const*, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 1 + }, + { + "name": "leveldb::port::Snappy_GetUncompressedLength(char const*, unsigned long, unsigned long*)", + "coverage": 0 + }, + { + "name": "leveldb::port::Snappy_Uncompress(char const*, unsigned long, char*)", + "coverage": 0 + }, + { + "name": "leveldb::port::GetHeapProfile(void (*)(void*, char const*, int), void*)", + "coverage": 0 + } + ] + }, + { + "name": "db.h", + "coverage": 0.3333333333333333, + "type": "objc", + "functions": [ + { + "name": "leveldb::Range::Range()", + "coverage": 0 + }, + { + "name": "leveldb::Range::Range(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::DB::DB()", + "coverage": 1 + } + ] + }, + { + "name": "logging.cc", + "coverage": 0.39215686274509803, + "type": "objc", + "functions": [ + { + "name": "leveldb::AppendNumberTo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::AppendEscapedStringTo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::NumberToString(unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::EscapeString(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::ConsumeDecimalNumber(leveldb::Slice*, unsigned long long*)", + "coverage": 0.8695652173913043 + } + ] + }, + { + "name": "dbformat.cc", + "coverage": 0.4247787610619469, + "type": "objc", + "functions": [ + { + "name": "leveldb::PackSequenceAndType(unsigned long long, leveldb::ValueType)", + "coverage": 1 + }, + { + "name": "leveldb::AppendInternalKey(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, leveldb::ParsedInternalKey const&)", + "coverage": 1 + }, + { + "name": "leveldb::ParsedInternalKey::DebugString() const", + "coverage": 0 + }, + { + "name": "leveldb::InternalKey::DebugString() const", + "coverage": 0 + }, + { + "name": "leveldb::InternalKeyComparator::Name() const", + "coverage": 0 + }, + { + "name": "leveldb::InternalKeyComparator::Compare(leveldb::Slice const&, leveldb::Slice const&) const", + "coverage": 1 + }, + { + "name": "leveldb::InternalKeyComparator::FindShortestSeparator(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, leveldb::Slice const&) const", + "coverage": 0.5625 + }, + { + "name": "leveldb::InternalKeyComparator::FindShortSuccessor(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 1 + }, + { + "name": "leveldb::InternalFilterPolicy::Name() const", + "coverage": 0 + }, + { + "name": "leveldb::InternalFilterPolicy::CreateFilter(leveldb::Slice const*, int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 0 + }, + { + "name": "leveldb::InternalFilterPolicy::KeyMayMatch(leveldb::Slice const&, leveldb::Slice const&) const", + "coverage": 0 + }, + { + "name": "leveldb::LookupKey::LookupKey(leveldb::Slice const&, unsigned long long)", + "coverage": 0 + } + ] + }, + { + "name": "iterator.cc", + "coverage": 0.4444444444444444, + "type": "objc", + "functions": [ + { + "name": "leveldb::Iterator::Iterator()", + "coverage": 1 + }, + { + "name": "leveldb::Iterator::~Iterator()", + "coverage": 0.5454545454545454 + }, + { + "name": "leveldb::Iterator::RegisterCleanup(void (*)(void*, void*), void*, void*)", + "coverage": 0.7142857142857143 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::EmptyIterator(leveldb::Status const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::Valid() const", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::Seek(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::SeekToFirst()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::SeekToLast()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::Next()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::Prev()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::key() const", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::value() const", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::EmptyIterator::status() const", + "coverage": 0 + }, + { + "name": "leveldb::NewEmptyIterator()", + "coverage": 0 + }, + { + "name": "leveldb::NewErrorIterator(leveldb::Status const&)", + "coverage": 0 + } + ] + }, + { + "name": "table.cc", + "coverage": 0.4458874458874459, + "type": "objc", + "functions": [ + { + "name": "leveldb::Table::Rep::~Rep()", + "coverage": 1 + }, + { + "name": "leveldb::Table::Open(leveldb::Options const&, leveldb::RandomAccessFile*, unsigned long long, leveldb::Table**)", + "coverage": 0.8775510204081632 + }, + { + "name": "leveldb::Table::ReadMeta(leveldb::Footer const&)", + "coverage": 0.14285714285714285 + }, + { + "name": "leveldb::Table::ReadFilter(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::Table::~Table()", + "coverage": 1 + }, + { + "name": "leveldb::DeleteBlock(void*, void*)", + "coverage": 1 + }, + { + "name": "leveldb::DeleteCachedBlock(leveldb::Slice const&, void*)", + "coverage": 0 + }, + { + "name": "leveldb::ReleaseBlock(void*, void*)", + "coverage": 0 + }, + { + "name": "leveldb::Table::BlockReader(void*, leveldb::ReadOptions const&, leveldb::Slice const&)", + "coverage": 0.7547169811320755 + }, + { + "name": "leveldb::Table::NewIterator(leveldb::ReadOptions const&) const", + "coverage": 1 + }, + { + "name": "leveldb::Table::InternalGet(leveldb::ReadOptions const&, leveldb::Slice const&, void*, void (*)(void*, leveldb::Slice const&, leveldb::Slice const&))", + "coverage": 0 + }, + { + "name": "leveldb::Table::ApproximateOffsetOf(leveldb::Slice const&) const", + "coverage": 0 + } + ] + }, + { + "name": "db_impl.cc", + "coverage": 0.4696969696969697, + "type": "objc", + "functions": [ + { + "name": "leveldb::DBImpl::Writer::Writer(leveldb::port::Mutex*)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::CompactionState::current_output()", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::CompactionState::CompactionState(leveldb::Compaction*)", + "coverage": 0 + }, + { + "name": "Definition at 86:57", + "coverage": 1 + }, + { + "name": "leveldb::SanitizeOptions(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::InternalKeyComparator const*, leveldb::InternalFilterPolicy const*, leveldb::Options const&)", + "coverage": 0.8695652173913043 + }, + { + "name": "leveldb::DBImpl::DBImpl(leveldb::Options const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::~DBImpl()", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::NewDB()", + "coverage": 0.8709677419354839 + }, + { + "name": "leveldb::DBImpl::MaybeIgnoreError(leveldb::Status*) const", + "coverage": 0.625 + }, + { + "name": "leveldb::DBImpl::DeleteObsoleteFiles()", + "coverage": 0.8181818181818182 + }, + { + "name": "leveldb::DBImpl::Recover(leveldb::VersionEdit*, bool*)", + "coverage": 0.7777777777777778 + }, + { + "name": "leveldb::DBImpl::RecoverLogFile(unsigned long long, bool, bool*, leveldb::VersionEdit*, unsigned long long*)", + "coverage": 0.7333333333333333 + }, + { + "name": "leveldb::DBImpl::RecoverLogFile(unsigned long long, bool, bool*, leveldb::VersionEdit*, unsigned long long*)::LogReporter::Corruption(unsigned long, leveldb::Status const&)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::WriteLevel0Table(leveldb::MemTable*, leveldb::VersionEdit*, leveldb::Version*)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::CompactMemTable()", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::CompactRange(leveldb::Slice const*, leveldb::Slice const*)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::TEST_CompactRange(int, leveldb::Slice const*, leveldb::Slice const*)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::TEST_CompactMemTable()", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::RecordBackgroundError(leveldb::Status const&)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::MaybeScheduleCompaction()", + "coverage": 0.8823529411764706 + }, + { + "name": "leveldb::DBImpl::BGWork(void*)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::BackgroundCall()", + "coverage": 0.8888888888888888 + }, + { + "name": "leveldb::DBImpl::BackgroundCompaction()", + "coverage": 0.08333333333333333 + }, + { + "name": "leveldb::DBImpl::CleanupCompaction(leveldb::DBImpl::CompactionState*)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::OpenCompactionOutputFile(leveldb::DBImpl::CompactionState*)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::FinishCompactionOutputFile(leveldb::DBImpl::CompactionState*, leveldb::Iterator*)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::InstallCompactionResults(leveldb::DBImpl::CompactionState*)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::DoCompactionWork(leveldb::DBImpl::CompactionState*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::CleanupIteratorState(void*, void*)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::NewInternalIterator(leveldb::ReadOptions const&, unsigned long long*, unsigned int*)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::TEST_NewInternalIterator()", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::TEST_MaxNextLevelOverlappingBytes()", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::Get(leveldb::ReadOptions const&, leveldb::Slice const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::NewIterator(leveldb::ReadOptions const&)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::RecordReadSample(leveldb::Slice)", + "coverage": 0.6666666666666666 + }, + { + "name": "leveldb::DBImpl::GetSnapshot()", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::ReleaseSnapshot(leveldb::Snapshot const*)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::Put(leveldb::WriteOptions const&, leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::Delete(leveldb::WriteOptions const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::Write(leveldb::WriteOptions const&, leveldb::WriteBatch*)", + "coverage": 0.7222222222222222 + }, + { + "name": "leveldb::DBImpl::BuildBatchGroup(leveldb::DBImpl::Writer**)", + "coverage": 0.4782608695652174 + }, + { + "name": "leveldb::DBImpl::MakeRoomForWrite(bool)", + "coverage": 0.6774193548387096 + }, + { + "name": "leveldb::DBImpl::GetProperty(leveldb::Slice const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 0 + }, + { + "name": "leveldb::DBImpl::GetApproximateSizes(leveldb::Range const*, int, unsigned long long*)", + "coverage": 0 + }, + { + "name": "leveldb::DB::Put(leveldb::WriteOptions const&, leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::DB::Delete(leveldb::WriteOptions const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::DB::~DB()", + "coverage": 1 + }, + { + "name": "leveldb::DB::Open(leveldb::Options const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::DB**)", + "coverage": 1 + }, + { + "name": "leveldb::Snapshot::~Snapshot()", + "coverage": 1 + }, + { + "name": "leveldb::DestroyDB(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::Options const&)", + "coverage": 0 + } + ] + }, + { + "name": "log_reader.cc", + "coverage": 0.47580645161290325, + "type": "objc", + "functions": [ + { + "name": "leveldb::log::Reader::Reporter::~Reporter()", + "coverage": 1 + }, + { + "name": "leveldb::log::Reader::Reader(leveldb::SequentialFile*, leveldb::log::Reader::Reporter*, bool, unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::log::Reader::~Reader()", + "coverage": 1 + }, + { + "name": "leveldb::log::Reader::SkipToInitialBlock()", + "coverage": 0 + }, + { + "name": "leveldb::log::Reader::ReadRecord(leveldb::Slice*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 0.4959349593495935 + }, + { + "name": "leveldb::log::Reader::LastRecordOffset()", + "coverage": 0 + }, + { + "name": "leveldb::log::Reader::ReportCorruption(unsigned long long, char const*)", + "coverage": 0 + }, + { + "name": "leveldb::log::Reader::ReportDrop(unsigned long long, leveldb::Status const&)", + "coverage": 0 + }, + { + "name": "leveldb::log::Reader::ReadPhysicalRecord(leveldb::Slice*)", + "coverage": 0.6024096385542169 + } + ] + }, + { + "name": "merger.cc", + "coverage": 0.48936170212765956, + "type": "objc", + "functions": [ + { + "name": "leveldb::(anonymous namespace)::MergingIterator::MergingIterator(leveldb::Comparator const*, leveldb::Iterator**, int)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::~MergingIterator()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::Valid() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::SeekToFirst()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::SeekToLast()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::Seek(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::Next()", + "coverage": 0.52 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::Prev()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::key() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::value() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::status() const", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::FindSmallest()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MergingIterator::FindLargest()", + "coverage": 0 + }, + { + "name": "leveldb::NewMergingIterator(leveldb::Comparator const*, leveldb::Iterator**, int)", + "coverage": 0.9 + } + ] + }, + { + "name": "version_set.cc", + "coverage": 0.4955022488755622, + "type": "objc", + "functions": [ + { + "name": "leveldb::TargetFileSize(leveldb::Options const*)", + "coverage": 1 + }, + { + "name": "leveldb::MaxGrandParentOverlapBytes(leveldb::Options const*)", + "coverage": 1 + }, + { + "name": "leveldb::ExpandedCompactionByteSizeLimit(leveldb::Options const*)", + "coverage": 0 + }, + { + "name": "leveldb::MaxBytesForLevel(leveldb::Options const*, int)", + "coverage": 1 + }, + { + "name": "leveldb::MaxFileSizeForLevel(leveldb::Options const*, int)", + "coverage": 0 + }, + { + "name": "leveldb::TotalFileSize(std::__1::vector<leveldb::FileMetaData*, std::__1::allocator<leveldb::FileMetaData*> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::Version::~Version()", + "coverage": 1 + }, + { + "name": "leveldb::FindFile(leveldb::InternalKeyComparator const&, std::__1::vector<leveldb::FileMetaData*, std::__1::allocator<leveldb::FileMetaData*> > const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::AfterFile(leveldb::Comparator const*, leveldb::Slice const*, leveldb::FileMetaData const*)", + "coverage": 0 + }, + { + "name": "leveldb::BeforeFile(leveldb::Comparator const*, leveldb::Slice const*, leveldb::FileMetaData const*)", + "coverage": 0 + }, + { + "name": "leveldb::SomeFileOverlapsRange(leveldb::InternalKeyComparator const&, bool, std::__1::vector<leveldb::FileMetaData*, std::__1::allocator<leveldb::FileMetaData*> > const&, leveldb::Slice const*, leveldb::Slice const*)", + "coverage": 0.6451612903225806 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::LevelFileNumIterator(leveldb::InternalKeyComparator const&, std::__1::vector<leveldb::FileMetaData*, std::__1::allocator<leveldb::FileMetaData*> > const*)", + "coverage": 1 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::Valid() const", + "coverage": 1 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::Seek(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::SeekToFirst()", + "coverage": 1 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::SeekToLast()", + "coverage": 0 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::Next()", + "coverage": 1 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::Prev()", + "coverage": 0 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::key() const", + "coverage": 1 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::value() const", + "coverage": 1 + }, + { + "name": "leveldb::Version::LevelFileNumIterator::status() const", + "coverage": 0 + }, + { + "name": "leveldb::GetFileIterator(void*, leveldb::ReadOptions const&, leveldb::Slice const&)", + "coverage": 0.8181818181818182 + }, + { + "name": "leveldb::Version::NewConcatenatingIterator(leveldb::ReadOptions const&, int) const", + "coverage": 1 + }, + { + "name": "leveldb::Version::AddIterators(leveldb::ReadOptions const&, std::__1::vector<leveldb::Iterator*, std::__1::allocator<leveldb::Iterator*> >*)", + "coverage": 0.7647058823529411 + }, + { + "name": "leveldb::SaveValue(void*, leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::NewestFirst(leveldb::FileMetaData*, leveldb::FileMetaData*)", + "coverage": 0 + }, + { + "name": "leveldb::Version::ForEachOverlapping(leveldb::Slice, leveldb::Slice, void*, bool (*)(void*, int, leveldb::FileMetaData*))", + "coverage": 0.6190476190476191 + }, + { + "name": "leveldb::Version::Get(leveldb::ReadOptions const&, leveldb::LookupKey const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, leveldb::Version::GetStats*)", + "coverage": 0 + }, + { + "name": "leveldb::Version::UpdateStats(leveldb::Version::GetStats const&)", + "coverage": 0 + }, + { + "name": "leveldb::Version::RecordReadSample(leveldb::Slice)", + "coverage": 0.8648648648648649 + }, + { + "name": "leveldb::Version::RecordReadSample(leveldb::Slice)::State::Match(void*, int, leveldb::FileMetaData*)", + "coverage": 1 + }, + { + "name": "leveldb::Version::Ref()", + "coverage": 1 + }, + { + "name": "leveldb::Version::Unref()", + "coverage": 1 + }, + { + "name": "leveldb::Version::OverlapInLevel(int, leveldb::Slice const*, leveldb::Slice const*)", + "coverage": 1 + }, + { + "name": "leveldb::Version::PickLevelForMemTableOutput(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0.84 + }, + { + "name": "leveldb::Version::GetOverlappingInputs(int, leveldb::InternalKey const*, leveldb::InternalKey const*, std::__1::vector<leveldb::FileMetaData*, std::__1::allocator<leveldb::FileMetaData*> >*)", + "coverage": 0.5526315789473685 + }, + { + "name": "leveldb::Version::DebugString() const", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::Builder::BySmallestKey::operator()(leveldb::FileMetaData*, leveldb::FileMetaData*) const", + "coverage": 0.6666666666666666 + }, + { + "name": "leveldb::VersionSet::Builder::Builder(leveldb::VersionSet*, leveldb::Version*)", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::Builder::~Builder()", + "coverage": 0.9 + }, + { + "name": "leveldb::VersionSet::Builder::Apply(leveldb::VersionEdit*)", + "coverage": 0.8181818181818182 + }, + { + "name": "leveldb::VersionSet::Builder::SaveTo(leveldb::Version*)", + "coverage": 0.7659574468085106 + }, + { + "name": "leveldb::VersionSet::Builder::MaybeAddFile(leveldb::Version*, int, leveldb::FileMetaData*)", + "coverage": 0.6428571428571429 + }, + { + "name": "leveldb::VersionSet::VersionSet(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::Options const*, leveldb::TableCache*, leveldb::InternalKeyComparator const*)", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::~VersionSet()", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::AppendVersion(leveldb::Version*)", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::LogAndApply(leveldb::VersionEdit*, leveldb::port::Mutex*)", + "coverage": 0.8214285714285714 + }, + { + "name": "leveldb::VersionSet::Recover(bool*)", + "coverage": 0.8925619834710744 + }, + { + "name": "leveldb::VersionSet::Recover(bool*)::LogReporter::Corruption(unsigned long, leveldb::Status const&)", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::ReuseManifest(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0.13793103448275862 + }, + { + "name": "leveldb::VersionSet::MarkFileNumberUsed(unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::Finalize(leveldb::Version*)", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::WriteSnapshot(leveldb::log::Writer*)", + "coverage": 0.8620689655172413 + }, + { + "name": "leveldb::VersionSet::NumLevelFiles(int) const", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::LevelSummary(leveldb::VersionSet::LevelSummaryStorage*) const", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::ApproximateOffsetOf(leveldb::Version*, leveldb::InternalKey const&)", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::AddLiveFiles(std::__1::set<unsigned long long, std::__1::less<unsigned long long>, std::__1::allocator<unsigned long long> >*)", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::NumLevelBytes(int) const", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::MaxNextLevelOverlappingBytes()", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::GetRange(std::__1::vector<leveldb::FileMetaData*, std::__1::allocator<leveldb::FileMetaData*> > const&, leveldb::InternalKey*, leveldb::InternalKey*)", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::GetRange2(std::__1::vector<leveldb::FileMetaData*, std::__1::allocator<leveldb::FileMetaData*> > const&, std::__1::vector<leveldb::FileMetaData*, std::__1::allocator<leveldb::FileMetaData*> > const&, leveldb::InternalKey*, leveldb::InternalKey*)", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::MakeInputIterator(leveldb::Compaction*)", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::PickCompaction()", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::SetupOtherInputs(leveldb::Compaction*)", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::CompactRange(int, leveldb::InternalKey const*, leveldb::InternalKey const*)", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::Compaction(leveldb::Options const*, int)", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::~Compaction()", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::IsTrivialMove() const", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::AddInputDeletions(leveldb::VersionEdit*)", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::IsBaseLevelForKey(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::ShouldStopBefore(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::ReleaseInputs()", + "coverage": 0 + } + ] + }, + { + "name": "cache.h", + "coverage": 0.5, + "type": "objc", + "functions": [ + { + "name": "leveldb::Cache::Cache()", + "coverage": 1 + }, + { + "name": "leveldb::Cache::Prune()", + "coverage": 0 + } + ] + }, + { + "name": "db_iter.cc", + "coverage": 0.5145631067961165, + "type": "objc", + "functions": [ + { + "name": "leveldb::(anonymous namespace)::DBIter::DBIter(leveldb::DBImpl*, leveldb::Comparator const*, leveldb::Iterator*, unsigned long long, unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::~DBIter()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::Valid() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::key() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::value() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::status() const", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::SaveKey(leveldb::Slice const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::ClearSavedValue()", + "coverage": 0.75 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::RandomPeriod()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::ParseKey(leveldb::ParsedInternalKey*)", + "coverage": 0.8666666666666667 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::Next()", + "coverage": 0.46153846153846156 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::FindNextUserEntry(bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::Prev()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::FindPrevUserEntry()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::Seek(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::SeekToFirst()", + "coverage": 0.8 + }, + { + "name": "leveldb::(anonymous namespace)::DBIter::SeekToLast()", + "coverage": 0 + }, + { + "name": "leveldb::NewDBIterator(leveldb::DBImpl*, leveldb::Comparator const*, leveldb::Iterator*, unsigned long long, unsigned int)", + "coverage": 1 + } + ] + }, + { + "name": "version_edit.cc", + "coverage": 0.5854700854700855, + "type": "objc", + "functions": [ + { + "name": "leveldb::VersionEdit::Clear()", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::EncodeTo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 0.8260869565217391 + }, + { + "name": "leveldb::GetInternalKey(leveldb::Slice*, leveldb::InternalKey*)", + "coverage": 0.7777777777777778 + }, + { + "name": "leveldb::GetLevel(leveldb::Slice*, int*)", + "coverage": 0.8 + }, + { + "name": "leveldb::VersionEdit::DecodeFrom(leveldb::Slice const&)", + "coverage": 0.6862745098039216 + }, + { + "name": "leveldb::VersionEdit::DebugString() const", + "coverage": 0 + } + ] + }, + { + "name": "version_set.h", + "coverage": 0.5925925925925926, + "type": "objc", + "functions": [ + { + "name": "leveldb::Version::NumFiles(int) const", + "coverage": 0 + }, + { + "name": "leveldb::Version::Version(leveldb::VersionSet*)", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::current() const", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::ManifestFileNumber() const", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::NewFileNumber()", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::ReuseFileNumber(unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::VersionSet::LastSequence() const", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::SetLastSequence(unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::LogNumber() const", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::PrevLogNumber() const", + "coverage": 1 + }, + { + "name": "leveldb::VersionSet::NeedsCompaction() const", + "coverage": 1 + }, + { + "name": "leveldb::Compaction::level() const", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::edit()", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::num_input_files(int) const", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::input(int, int) const", + "coverage": 0 + }, + { + "name": "leveldb::Compaction::MaxOutputFileSize() const", + "coverage": 0 + } + ] + }, + { + "name": "dbformat.h", + "coverage": 0.6037735849056604, + "type": "objc", + "functions": [ + { + "name": "leveldb::ParsedInternalKey::ParsedInternalKey()", + "coverage": 1 + }, + { + "name": "leveldb::ParsedInternalKey::ParsedInternalKey(leveldb::Slice const&, unsigned long long const&, leveldb::ValueType)", + "coverage": 1 + }, + { + "name": "leveldb::InternalKeyEncodingLength(leveldb::ParsedInternalKey const&)", + "coverage": 0 + }, + { + "name": "leveldb::ExtractUserKey(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::ExtractValueType(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::InternalKeyComparator::InternalKeyComparator(leveldb::Comparator const*)", + "coverage": 1 + }, + { + "name": "leveldb::InternalKeyComparator::user_comparator() const", + "coverage": 1 + }, + { + "name": "leveldb::InternalFilterPolicy::InternalFilterPolicy(leveldb::FilterPolicy const*)", + "coverage": 1 + }, + { + "name": "leveldb::InternalKey::InternalKey()", + "coverage": 1 + }, + { + "name": "leveldb::InternalKey::InternalKey(leveldb::Slice const&, unsigned long long, leveldb::ValueType)", + "coverage": 1 + }, + { + "name": "leveldb::InternalKey::DecodeFrom(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::InternalKey::Encode() const", + "coverage": 1 + }, + { + "name": "leveldb::InternalKey::user_key() const", + "coverage": 1 + }, + { + "name": "leveldb::InternalKey::SetFrom(leveldb::ParsedInternalKey const&)", + "coverage": 0 + }, + { + "name": "leveldb::InternalKey::Clear()", + "coverage": 0 + }, + { + "name": "leveldb::InternalKeyComparator::Compare(leveldb::InternalKey const&, leveldb::InternalKey const&) const", + "coverage": 1 + }, + { + "name": "leveldb::ParseInternalKey(leveldb::Slice const&, leveldb::ParsedInternalKey*)", + "coverage": 1 + }, + { + "name": "leveldb::LookupKey::memtable_key() const", + "coverage": 0 + }, + { + "name": "leveldb::LookupKey::internal_key() const", + "coverage": 0 + }, + { + "name": "leveldb::LookupKey::user_key() const", + "coverage": 0 + }, + { + "name": "leveldb::LookupKey::~LookupKey()", + "coverage": 0 + } + ] + }, + { + "name": "memtable.cc", + "coverage": 0.6161616161616161, + "type": "objc", + "functions": [ + { + "name": "leveldb::GetLengthPrefixedSlice(char const*)", + "coverage": 1 + }, + { + "name": "leveldb::MemTable::MemTable(leveldb::InternalKeyComparator const&)", + "coverage": 1 + }, + { + "name": "leveldb::MemTable::~MemTable()", + "coverage": 1 + }, + { + "name": "leveldb::MemTable::ApproximateMemoryUsage()", + "coverage": 1 + }, + { + "name": "leveldb::MemTable::KeyComparator::operator()(char const*, char const*) const", + "coverage": 1 + }, + { + "name": "leveldb::EncodeKey(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::MemTableIterator::MemTableIterator(leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>*)", + "coverage": 1 + }, + { + "name": "leveldb::MemTableIterator::Valid() const", + "coverage": 1 + }, + { + "name": "leveldb::MemTableIterator::Seek(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::MemTableIterator::SeekToFirst()", + "coverage": 1 + }, + { + "name": "leveldb::MemTableIterator::SeekToLast()", + "coverage": 0 + }, + { + "name": "leveldb::MemTableIterator::Next()", + "coverage": 1 + }, + { + "name": "leveldb::MemTableIterator::Prev()", + "coverage": 0 + }, + { + "name": "leveldb::MemTableIterator::key() const", + "coverage": 1 + }, + { + "name": "leveldb::MemTableIterator::value() const", + "coverage": 1 + }, + { + "name": "leveldb::MemTableIterator::status() const", + "coverage": 1 + }, + { + "name": "leveldb::MemTable::NewIterator()", + "coverage": 1 + }, + { + "name": "leveldb::MemTable::Add(unsigned long long, leveldb::ValueType, leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::MemTable::Get(leveldb::LookupKey const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, leveldb::Status*)", + "coverage": 0 + } + ] + }, + { + "name": "status.h", + "coverage": 0.631578947368421, + "type": "objc", + "functions": [ + { + "name": "leveldb::Status::Status()", + "coverage": 1 + }, + { + "name": "leveldb::Status::~Status()", + "coverage": 1 + }, + { + "name": "leveldb::Status::OK()", + "coverage": 1 + }, + { + "name": "leveldb::Status::NotFound(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::Status::Corruption(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::Status::NotSupported(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::Status::InvalidArgument(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::Status::IOError(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::Status::ok() const", + "coverage": 1 + }, + { + "name": "leveldb::Status::IsNotFound() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::IsCorruption() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::IsIOError() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::IsNotSupportedError() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::IsInvalidArgument() const", + "coverage": 0 + }, + { + "name": "leveldb::Status::code() const", + "coverage": 1 + }, + { + "name": "leveldb::Status::Status(leveldb::Status const&)", + "coverage": 1 + }, + { + "name": "leveldb::Status::operator=(leveldb::Status const&)", + "coverage": 1 + } + ] + }, + { + "name": "block.cc", + "coverage": 0.6354166666666666, + "type": "objc", + "functions": [ + { + "name": "leveldb::Block::NumRestarts() const", + "coverage": 1 + }, + { + "name": "leveldb::Block::Block(leveldb::BlockContents const&)", + "coverage": 0.7692307692307693 + }, + { + "name": "leveldb::Block::~Block()", + "coverage": 0.6 + }, + { + "name": "leveldb::DecodeEntry(char const*, char const*, unsigned int*, unsigned int*, unsigned int*)", + "coverage": 0.8947368421052632 + }, + { + "name": "leveldb::Block::Iter::Compare(leveldb::Slice const&, leveldb::Slice const&) const", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::NextEntryOffset() const", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::GetRestartPoint(unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::SeekToRestartPoint(unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::Iter(leveldb::Comparator const*, char const*, unsigned int, unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::Valid() const", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::status() const", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::key() const", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::value() const", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::Next()", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::Prev()", + "coverage": 0 + }, + { + "name": "leveldb::Block::Iter::Seek(leveldb::Slice const&)", + "coverage": 0.41025641025641024 + }, + { + "name": "leveldb::Block::Iter::SeekToFirst()", + "coverage": 1 + }, + { + "name": "leveldb::Block::Iter::SeekToLast()", + "coverage": 0 + }, + { + "name": "leveldb::Block::Iter::CorruptionError()", + "coverage": 0 + }, + { + "name": "leveldb::Block::Iter::ParseNextKey()", + "coverage": 0.8571428571428571 + }, + { + "name": "leveldb::Block::NewIterator(leveldb::Comparator const*)", + "coverage": 0.7272727272727273 + } + ] + }, + { + "name": "table_cache.cc", + "coverage": 0.6470588235294118, + "type": "objc", + "functions": [ + { + "name": "leveldb::DeleteEntry(leveldb::Slice const&, void*)", + "coverage": 1 + }, + { + "name": "leveldb::UnrefEntry(void*, void*)", + "coverage": 1 + }, + { + "name": "leveldb::TableCache::TableCache(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::Options const*, int)", + "coverage": 1 + }, + { + "name": "leveldb::TableCache::~TableCache()", + "coverage": 1 + }, + { + "name": "leveldb::TableCache::FindTable(unsigned long long, unsigned long long, leveldb::Cache::Handle**)", + "coverage": 0.7428571428571429 + }, + { + "name": "leveldb::TableCache::NewIterator(leveldb::ReadOptions const&, unsigned long long, unsigned long long, leveldb::Table**)", + "coverage": 0.6842105263157895 + }, + { + "name": "leveldb::TableCache::Get(leveldb::ReadOptions const&, unsigned long long, unsigned long long, leveldb::Slice const&, void*, void (*)(void*, leveldb::Slice const&, leveldb::Slice const&))", + "coverage": 0 + }, + { + "name": "leveldb::TableCache::Evict(unsigned long long)", + "coverage": 0 + } + ] + }, + { + "name": "env_posix.cc", + "coverage": 0.6597077244258872, + "type": "objc", + "functions": [ + { + "name": "leveldb::(anonymous namespace)::IOError(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::Limiter::Limiter(long)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::Limiter::Acquire()", + "coverage": 0.7692307692307693 + }, + { + "name": "leveldb::(anonymous namespace)::Limiter::Release()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::Limiter::GetAllowed() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::Limiter::SetAllowed(long)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixSequentialFile::PosixSequentialFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, __sFILE*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixSequentialFile::~PosixSequentialFile()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixSequentialFile::Read(unsigned long, leveldb::Slice*, char*)", + "coverage": 0.7857142857142857 + }, + { + "name": "leveldb::(anonymous namespace)::PosixSequentialFile::Skip(unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixRandomAccessFile::PosixRandomAccessFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, leveldb::(anonymous namespace)::Limiter*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixRandomAccessFile::~PosixRandomAccessFile()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixRandomAccessFile::Read(unsigned long long, unsigned long, leveldb::Slice*, char*) const", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixMmapReadableFile::PosixMmapReadableFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, void*, unsigned long, leveldb::(anonymous namespace)::Limiter*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixMmapReadableFile::~PosixMmapReadableFile()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixMmapReadableFile::Read(unsigned long long, unsigned long, leveldb::Slice*, char*) const", + "coverage": 0.8 + }, + { + "name": "leveldb::(anonymous namespace)::PosixWritableFile::PosixWritableFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, __sFILE*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixWritableFile::~PosixWritableFile()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixWritableFile::Append(leveldb::Slice const&)", + "coverage": 0.7142857142857143 + }, + { + "name": "leveldb::(anonymous namespace)::PosixWritableFile::Close()", + "coverage": 0.75 + }, + { + "name": "leveldb::(anonymous namespace)::PosixWritableFile::Flush()", + "coverage": 0.6666666666666666 + }, + { + "name": "leveldb::(anonymous namespace)::PosixWritableFile::SyncDirIfManifest()", + "coverage": 0.8076923076923077 + }, + { + "name": "leveldb::(anonymous namespace)::PosixWritableFile::Sync()", + "coverage": 0.6666666666666666 + }, + { + "name": "leveldb::(anonymous namespace)::LockOrUnlock(int, bool)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixLockTable::Insert(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixLockTable::Remove(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::~PosixEnv()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::NewSequentialFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::SequentialFile**)", + "coverage": 0.8 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::NewRandomAccessFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::RandomAccessFile**)", + "coverage": 0.7307692307692307 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::NewWritableFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile**)", + "coverage": 0.8181818181818182 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::NewAppendableFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile**)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::FileExists(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::GetChildren(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >*)", + "coverage": 0.8461538461538461 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::DeleteFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0.7142857142857143 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::CreateDir(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::DeleteDir(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::GetFileSize(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long*)", + "coverage": 0.8181818181818182 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::RenameFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::LockFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::FileLock**)", + "coverage": 0.7142857142857143 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::UnlockFile(leveldb::FileLock*)", + "coverage": 0.8181818181818182 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::GetTestDirectory(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::gettid()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::NewLogger(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::Logger**)", + "coverage": 0.8 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::NowMicros()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::SleepForMicroseconds(int)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::PthreadCall(char const*, int)", + "coverage": 0.5 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::BGThreadWrapper(void*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MaxMmaps()", + "coverage": 0.75 + }, + { + "name": "leveldb::(anonymous namespace)::MaxOpenFiles()", + "coverage": 0.6875 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::PosixEnv()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::Schedule(void (*)(void*), void*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::BGThread()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::StartThreadWrapper(void*)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::PosixEnv::StartThread(void (*)(void*), void*)", + "coverage": 0 + }, + { + "name": "leveldb::InitDefaultEnv()", + "coverage": 1 + }, + { + "name": "leveldb::EnvPosixTestHelper::SetReadOnlyFDLimit(int)", + "coverage": 0 + }, + { + "name": "leveldb::EnvPosixTestHelper::SetReadOnlyMMapLimit(int)", + "coverage": 0 + }, + { + "name": "leveldb::Env::Default()", + "coverage": 1 + } + ] + }, + { + "name": "format.cc", + "coverage": 0.6611570247933884, + "type": "objc", + "functions": [ + { + "name": "leveldb::BlockHandle::EncodeTo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 1 + }, + { + "name": "leveldb::BlockHandle::DecodeFrom(leveldb::Slice*)", + "coverage": 0.75 + }, + { + "name": "leveldb::Footer::EncodeTo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 1 + }, + { + "name": "leveldb::Footer::DecodeFrom(leveldb::Slice*)", + "coverage": 0.9047619047619048 + }, + { + "name": "leveldb::ReadBlock(leveldb::RandomAccessFile*, leveldb::ReadOptions const&, leveldb::BlockHandle const&, leveldb::BlockContents*)", + "coverage": 0.5066666666666667 + } + ] + }, + { + "name": "hash.cc", + "coverage": 0.6875, + "type": "objc", + "functions": [ + { + "name": "leveldb::Hash(char const*, unsigned long, unsigned int)", + "coverage": 0.6875 + } + ] + }, + { + "name": "cache.cc", + "coverage": 0.6882591093117408, + "type": "objc", + "functions": [ + { + "name": "leveldb::Cache::~Cache()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUHandle::key() const", + "coverage": 0.8888888888888888 + }, + { + "name": "leveldb::(anonymous namespace)::HandleTable::HandleTable()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::HandleTable::~HandleTable()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::HandleTable::Lookup(leveldb::Slice const&, unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::HandleTable::Insert(leveldb::(anonymous namespace)::LRUHandle*)", + "coverage": 0.7333333333333333 + }, + { + "name": "leveldb::(anonymous namespace)::HandleTable::Remove(leveldb::Slice const&, unsigned int)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::HandleTable::FindPointer(leveldb::Slice const&, unsigned int)", + "coverage": 0.75 + }, + { + "name": "leveldb::(anonymous namespace)::HandleTable::Resize()", + "coverage": 0.48 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::SetCapacity(unsigned long)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::TotalCharge() const", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::LRUCache()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::~LRUCache()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::Ref(leveldb::(anonymous namespace)::LRUHandle*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::Unref(leveldb::(anonymous namespace)::LRUHandle*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::LRU_Remove(leveldb::(anonymous namespace)::LRUHandle*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::LRU_Append(leveldb::(anonymous namespace)::LRUHandle*, leveldb::(anonymous namespace)::LRUHandle*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::Lookup(leveldb::Slice const&, unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::Release(leveldb::Cache::Handle*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::Insert(leveldb::Slice const&, unsigned int, void*, unsigned long, void (*)(leveldb::Slice const&, void*))", + "coverage": 0.7878787878787878 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::FinishErase(leveldb::(anonymous namespace)::LRUHandle*)", + "coverage": 0.4 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::Erase(leveldb::Slice const&, unsigned int)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::LRUCache::Prune()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::HashSlice(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::Shard(unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::ShardedLRUCache(unsigned long)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::~ShardedLRUCache()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::Insert(leveldb::Slice const&, void*, unsigned long, void (*)(leveldb::Slice const&, void*))", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::Lookup(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::Release(leveldb::Cache::Handle*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::Erase(leveldb::Slice const&)", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::Value(leveldb::Cache::Handle*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::NewId()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::Prune()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::ShardedLRUCache::TotalCharge() const", + "coverage": 0 + }, + { + "name": "leveldb::NewLRUCache(unsigned long)", + "coverage": 1 + } + ] + }, + { + "name": "skiplist.h", + "coverage": 0.7142857142857143, + "type": "objc", + "functions": [ + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::GetMaxHeight() const", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Equal(char const* const&, char const* const&) const", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Node::Node(char const* const&)", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Node::Next(int)", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Node::SetNext(int, leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Node*)", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Node::NoBarrier_Next(int)", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Node::NoBarrier_SetNext(int, leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Node*)", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::NewNode(char const* const&, int)", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Iterator::Iterator(leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator> const*)", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Iterator::Valid() const", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Iterator::key() const", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Iterator::Next()", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Iterator::Prev()", + "coverage": 0 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Iterator::Seek(char const* const&)", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Iterator::SeekToFirst()", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Iterator::SeekToLast()", + "coverage": 0 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::RandomHeight()", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::KeyIsAfterNode(char const* const&, leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Node*) const", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::FindGreaterOrEqual(char const* const&, leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Node**) const", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::FindLessThan(char const* const&) const", + "coverage": 0 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::FindLast() const", + "coverage": 0 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::SkipList(leveldb::MemTable::KeyComparator, leveldb::Arena*)", + "coverage": 1 + }, + { + "name": "leveldb::SkipList<char const*, leveldb::MemTable::KeyComparator>::Insert(char const* const&)", + "coverage": 1 + } + ] + }, + { + "name": "two_level_iterator.cc", + "coverage": 0.7352941176470589, + "type": "objc", + "functions": [ + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::Valid() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::key() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::value() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::status() const", + "coverage": 0.8 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::SaveError(leveldb::Status const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::TwoLevelIterator(leveldb::Iterator*, leveldb::Iterator* (*)(void*, leveldb::ReadOptions const&, leveldb::Slice const&), void*, leveldb::ReadOptions const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::~TwoLevelIterator()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::Seek(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::SeekToFirst()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::SeekToLast()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::Next()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::Prev()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::SkipEmptyDataBlocksForward()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::SkipEmptyDataBlocksBackward()", + "coverage": 0 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::SetDataIterator(leveldb::Iterator*)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::TwoLevelIterator::InitDataBlock()", + "coverage": 0.8666666666666667 + }, + { + "name": "leveldb::NewTwoLevelIterator(leveldb::Iterator*, leveldb::Iterator* (*)(void*, leveldb::ReadOptions const&, leveldb::Slice const&), void*, leveldb::ReadOptions const&)", + "coverage": 1 + } + ] + }, + { + "name": "random.h", + "coverage": 0.75, + "type": "objc", + "functions": [ + { + "name": "leveldb::Random::Random(unsigned int)", + "coverage": 0.6666666666666666 + }, + { + "name": "leveldb::Random::Next()", + "coverage": 0.9047619047619048 + }, + { + "name": "leveldb::Random::Uniform(int)", + "coverage": 1 + }, + { + "name": "leveldb::Random::OneIn(int)", + "coverage": 0 + }, + { + "name": "leveldb::Random::Skewed(int)", + "coverage": 0 + } + ] + }, + { + "name": "env.cc", + "coverage": 0.7567567567567568, + "type": "objc", + "functions": [ + { + "name": "leveldb::Env::~Env()", + "coverage": 0 + }, + { + "name": "leveldb::Env::NewAppendableFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::WritableFile**)", + "coverage": 0 + }, + { + "name": "leveldb::SequentialFile::~SequentialFile()", + "coverage": 1 + }, + { + "name": "leveldb::RandomAccessFile::~RandomAccessFile()", + "coverage": 1 + }, + { + "name": "leveldb::WritableFile::~WritableFile()", + "coverage": 1 + }, + { + "name": "leveldb::Logger::~Logger()", + "coverage": 1 + }, + { + "name": "leveldb::FileLock::~FileLock()", + "coverage": 1 + }, + { + "name": "leveldb::Log(leveldb::Logger*, char const*, ...)", + "coverage": 1 + }, + { + "name": "leveldb::DoWriteStringToFile(leveldb::Env*, leveldb::Slice const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool)", + "coverage": 0.7894736842105263 + }, + { + "name": "leveldb::WriteStringToFile(leveldb::Env*, leveldb::Slice const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 0 + }, + { + "name": "leveldb::WriteStringToFileSync(leveldb::Env*, leveldb::Slice const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::ReadFileToString(leveldb::Env*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*)", + "coverage": 0.8333333333333334 + }, + { + "name": "leveldb::EnvWrapper::~EnvWrapper()", + "coverage": 0 + } + ] + }, + { + "name": "port_posix.cc", + "coverage": 0.7692307692307693, + "type": "objc", + "functions": [ + { + "name": "leveldb::port::PthreadCall(char const*, int)", + "coverage": 0.5 + }, + { + "name": "leveldb::port::Mutex::Mutex()", + "coverage": 1 + }, + { + "name": "leveldb::port::Mutex::~Mutex()", + "coverage": 1 + }, + { + "name": "leveldb::port::Mutex::Lock()", + "coverage": 1 + }, + { + "name": "leveldb::port::Mutex::Unlock()", + "coverage": 1 + }, + { + "name": "leveldb::port::CondVar::CondVar(leveldb::port::Mutex*)", + "coverage": 1 + }, + { + "name": "leveldb::port::CondVar::~CondVar()", + "coverage": 1 + }, + { + "name": "leveldb::port::CondVar::Wait()", + "coverage": 1 + }, + { + "name": "leveldb::port::CondVar::Signal()", + "coverage": 0 + }, + { + "name": "leveldb::port::CondVar::SignalAll()", + "coverage": 1 + }, + { + "name": "leveldb::port::InitOnce(_opaque_pthread_once_t*, void (*)())", + "coverage": 1 + } + ] + }, + { + "name": "status.cc", + "coverage": 0.7704918032786885, + "type": "objc", + "functions": [ + { + "name": "leveldb::Status::CopyState(char const*)", + "coverage": 1 + }, + { + "name": "leveldb::Status::Status(leveldb::Status::Code, leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::Status::ToString() const", + "coverage": 0.631578947368421 + } + ] + }, + { + "name": "table_builder.cc", + "coverage": 0.7846153846153846, + "type": "objc", + "functions": [ + { + "name": "leveldb::TableBuilder::Rep::Rep(leveldb::Options const&, leveldb::WritableFile*)", + "coverage": 1 + }, + { + "name": "leveldb::TableBuilder::TableBuilder(leveldb::Options const&, leveldb::WritableFile*)", + "coverage": 0.6 + }, + { + "name": "leveldb::TableBuilder::~TableBuilder()", + "coverage": 1 + }, + { + "name": "leveldb::TableBuilder::ChangeOptions(leveldb::Options const&)", + "coverage": 0 + }, + { + "name": "leveldb::TableBuilder::Add(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 0.9333333333333333 + }, + { + "name": "leveldb::TableBuilder::Flush()", + "coverage": 0.8666666666666667 + }, + { + "name": "leveldb::TableBuilder::WriteBlock(leveldb::BlockBuilder*, leveldb::BlockHandle*)", + "coverage": 0.9142857142857143 + }, + { + "name": "leveldb::TableBuilder::WriteRawBlock(leveldb::Slice const&, leveldb::CompressionType, leveldb::BlockHandle*)", + "coverage": 1 + }, + { + "name": "leveldb::TableBuilder::status() const", + "coverage": 1 + }, + { + "name": "leveldb::TableBuilder::Finish()", + "coverage": 0.8214285714285714 + }, + { + "name": "leveldb::TableBuilder::Abandon()", + "coverage": 0 + }, + { + "name": "leveldb::TableBuilder::NumEntries() const", + "coverage": 0 + }, + { + "name": "leveldb::TableBuilder::FileSize() const", + "coverage": 1 + } + ] + }, + { + "name": "coding.cc", + "coverage": 0.8132530120481928, + "type": "objc", + "functions": [ + { + "name": "leveldb::EncodeFixed32(char*, unsigned int)", + "coverage": 0.5 + }, + { + "name": "leveldb::EncodeFixed64(char*, unsigned long long)", + "coverage": 0.35714285714285715 + }, + { + "name": "leveldb::PutFixed32(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::PutFixed64(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::EncodeVarint32(char*, unsigned int)", + "coverage": 0.7777777777777778 + }, + { + "name": "leveldb::PutVarint32(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::EncodeVarint64(char*, unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::PutVarint64(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::PutLengthPrefixedSlice(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::VarintLength(unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::GetVarint32PtrFallback(char const*, char const*, unsigned int*)", + "coverage": 1 + }, + { + "name": "leveldb::GetVarint32(leveldb::Slice*, unsigned int*)", + "coverage": 1 + }, + { + "name": "leveldb::GetVarint64Ptr(char const*, char const*, unsigned long long*)", + "coverage": 1 + }, + { + "name": "leveldb::GetVarint64(leveldb::Slice*, unsigned long long*)", + "coverage": 0.9090909090909091 + }, + { + "name": "leveldb::GetLengthPrefixedSlice(char const*, char const*, leveldb::Slice*)", + "coverage": 0 + }, + { + "name": "leveldb::GetLengthPrefixedSlice(leveldb::Slice*, leveldb::Slice*)", + "coverage": 0.8181818181818182 + } + ] + }, + { + "name": "version_edit.h", + "coverage": 0.8378378378378378, + "type": "objc", + "functions": [ + { + "name": "leveldb::FileMetaData::FileMetaData()", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::VersionEdit()", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::~VersionEdit()", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::SetComparatorName(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::SetLogNumber(unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::SetPrevLogNumber(unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::SetNextFile(unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::SetLastSequence(unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::SetCompactPointer(int, leveldb::InternalKey const&)", + "coverage": 0 + }, + { + "name": "leveldb::VersionEdit::AddFile(int, unsigned long long, unsigned long long, leveldb::InternalKey const&, leveldb::InternalKey const&)", + "coverage": 1 + }, + { + "name": "leveldb::VersionEdit::DeleteFile(int, unsigned long long)", + "coverage": 0 + } + ] + }, + { + "name": "posix_logger.h", + "coverage": 0.8428571428571429, + "type": "objc", + "functions": [ + { + "name": "leveldb::PosixLogger::PosixLogger(__sFILE*, unsigned long long (*)())", + "coverage": 1 + }, + { + "name": "leveldb::PosixLogger::~PosixLogger()", + "coverage": 1 + }, + { + "name": "leveldb::PosixLogger::Logv(char const*, __va_list_tag*)", + "coverage": 0.8333333333333334 + } + ] + }, + { + "name": "filename.cc", + "coverage": 0.8613861386138614, + "type": "objc", + "functions": [ + { + "name": "leveldb::MakeFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long, char const*)", + "coverage": 1 + }, + { + "name": "leveldb::LogFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::TableFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::SSTTableFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::DescriptorFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::CurrentFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::LockFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::TempFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::InfoLogFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::OldInfoLogFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::ParseFileName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long*, leveldb::FileType*)", + "coverage": 0.813953488372093 + }, + { + "name": "leveldb::SetCurrentFile(leveldb::Env*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long long)", + "coverage": 0.875 + } + ] + }, + { + "name": "write_batch.cc", + "coverage": 0.8617021276595744, + "type": "objc", + "functions": [ + { + "name": "leveldb::WriteBatch::WriteBatch()", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatch::~WriteBatch()", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatch::Handler::~Handler()", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatch::Clear()", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatch::Iterate(leveldb::WriteBatch::Handler*) const", + "coverage": 0.7948717948717948 + }, + { + "name": "leveldb::WriteBatchInternal::Count(leveldb::WriteBatch const*)", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatchInternal::SetCount(leveldb::WriteBatch*, int)", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatchInternal::Sequence(leveldb::WriteBatch const*)", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatchInternal::SetSequence(leveldb::WriteBatch*, unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatch::Put(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatch::Delete(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MemTableInserter::Put(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::MemTableInserter::Delete(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatchInternal::InsertInto(leveldb::WriteBatch const*, leveldb::MemTable*)", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatchInternal::SetContents(leveldb::WriteBatch*, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatchInternal::Append(leveldb::WriteBatch*, leveldb::WriteBatch const*)", + "coverage": 0 + } + ] + }, + { + "name": "builder.cc", + "coverage": 0.8769230769230769, + "type": "objc", + "functions": [ + { + "name": "leveldb::BuildTable(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, leveldb::Env*, leveldb::Options const&, leveldb::TableCache*, leveldb::Iterator*, leveldb::FileMetaData*)", + "coverage": 0.8769230769230769 + } + ] + }, + { + "name": "coding.h", + "coverage": 0.8857142857142857, + "type": "objc", + "functions": [ + { + "name": "leveldb::DecodeFixed32(char const*)", + "coverage": 1 + }, + { + "name": "leveldb::DecodeFixed64(char const*)", + "coverage": 0.6666666666666666 + }, + { + "name": "leveldb::GetVarint32Ptr(char const*, char const*, unsigned int*)", + "coverage": 1 + } + ] + }, + { + "name": "comparator.cc", + "coverage": 0.9183673469387755, + "type": "objc", + "functions": [ + { + "name": "leveldb::Comparator::~Comparator()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::BytewiseComparatorImpl::BytewiseComparatorImpl()", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::BytewiseComparatorImpl::Name() const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::BytewiseComparatorImpl::Compare(leveldb::Slice const&, leveldb::Slice const&) const", + "coverage": 1 + }, + { + "name": "leveldb::(anonymous namespace)::BytewiseComparatorImpl::FindShortestSeparator(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, leveldb::Slice const&) const", + "coverage": 0.8095238095238095 + }, + { + "name": "leveldb::(anonymous namespace)::BytewiseComparatorImpl::FindShortSuccessor(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*) const", + "coverage": 1 + }, + { + "name": "leveldb::InitModule()", + "coverage": 1 + }, + { + "name": "leveldb::BytewiseComparator()", + "coverage": 1 + } + ] + }, + { + "name": "log_writer.cc", + "coverage": 0.9195402298850575, + "type": "objc", + "functions": [ + { + "name": "leveldb::log::InitTypeCrc(unsigned int*)", + "coverage": 1 + }, + { + "name": "leveldb::log::Writer::Writer(leveldb::WritableFile*)", + "coverage": 1 + }, + { + "name": "leveldb::log::Writer::Writer(leveldb::WritableFile*, unsigned long long)", + "coverage": 0 + }, + { + "name": "leveldb::log::Writer::~Writer()", + "coverage": 1 + }, + { + "name": "leveldb::log::Writer::AddRecord(leveldb::Slice const&)", + "coverage": 0.9148936170212766 + }, + { + "name": "leveldb::log::Writer::EmitPhysicalRecord(leveldb::log::RecordType, char const*, unsigned long)", + "coverage": 1 + } + ] + }, + { + "name": "iterator_wrapper.h", + "coverage": 0.9333333333333333, + "type": "objc", + "functions": [ + { + "name": "leveldb::IteratorWrapper::IteratorWrapper()", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::IteratorWrapper(leveldb::Iterator*)", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::~IteratorWrapper()", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::iter() const", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::Set(leveldb::Iterator*)", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::Valid() const", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::key() const", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::value() const", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::status() const", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::Next()", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::Prev()", + "coverage": 0 + }, + { + "name": "leveldb::IteratorWrapper::Seek(leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::SeekToFirst()", + "coverage": 1 + }, + { + "name": "leveldb::IteratorWrapper::SeekToLast()", + "coverage": 0 + }, + { + "name": "leveldb::IteratorWrapper::Update()", + "coverage": 1 + } + ] + }, + { + "name": "crc32c.cc", + "coverage": 0.9666666666666667, + "type": "objc", + "functions": [ + { + "name": "leveldb::crc32c::LE_LOAD32(unsigned char const*)", + "coverage": 1 + }, + { + "name": "leveldb::crc32c::CanAccelerateCRC32C()", + "coverage": 1 + }, + { + "name": "leveldb::crc32c::Extend(unsigned int, char const*, unsigned long)", + "coverage": 0.9591836734693877 + } + ] + }, + { + "name": "mutexlock.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::MutexLock::MutexLock(leveldb::port::Mutex*)", + "coverage": 1 + }, + { + "name": "leveldb::MutexLock::~MutexLock()", + "coverage": 1 + } + ] + }, + { + "name": "options.cc", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::Options::Options()", + "coverage": 1 + } + ] + }, + { + "name": "options.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::ReadOptions::ReadOptions()", + "coverage": 1 + }, + { + "name": "leveldb::WriteOptions::WriteOptions()", + "coverage": 1 + } + ] + }, + { + "name": "write_batch_internal.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::WriteBatchInternal::Contents(leveldb::WriteBatch const*)", + "coverage": 1 + }, + { + "name": "leveldb::WriteBatchInternal::ByteSize(leveldb::WriteBatch const*)", + "coverage": 1 + } + ] + }, + { + "name": "db_impl.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::DBImpl::CompactionStats::CompactionStats()", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::CompactionStats::Add(leveldb::DBImpl::CompactionStats const&)", + "coverage": 1 + }, + { + "name": "leveldb::DBImpl::user_comparator() const", + "coverage": 1 + } + ] + }, + { + "name": "atomic_pointer.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::port::MemoryBarrier()", + "coverage": 1 + }, + { + "name": "leveldb::port::AtomicPointer::AtomicPointer()", + "coverage": 1 + }, + { + "name": "leveldb::port::AtomicPointer::AtomicPointer(void*)", + "coverage": 1 + }, + { + "name": "leveldb::port::AtomicPointer::NoBarrier_Load() const", + "coverage": 1 + }, + { + "name": "leveldb::port::AtomicPointer::NoBarrier_Store(void*)", + "coverage": 1 + }, + { + "name": "leveldb::port::AtomicPointer::Acquire_Load() const", + "coverage": 1 + }, + { + "name": "leveldb::port::AtomicPointer::Release_Store(void*)", + "coverage": 1 + } + ] + }, + { + "name": "format.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::BlockHandle::offset() const", + "coverage": 1 + }, + { + "name": "leveldb::BlockHandle::set_offset(unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::BlockHandle::size() const", + "coverage": 1 + }, + { + "name": "leveldb::BlockHandle::set_size(unsigned long long)", + "coverage": 1 + }, + { + "name": "leveldb::Footer::Footer()", + "coverage": 1 + }, + { + "name": "leveldb::Footer::metaindex_handle() const", + "coverage": 1 + }, + { + "name": "leveldb::Footer::set_metaindex_handle(leveldb::BlockHandle const&)", + "coverage": 1 + }, + { + "name": "leveldb::Footer::index_handle() const", + "coverage": 1 + }, + { + "name": "leveldb::Footer::set_index_handle(leveldb::BlockHandle const&)", + "coverage": 1 + }, + { + "name": "leveldb::BlockHandle::BlockHandle()", + "coverage": 1 + } + ] + }, + { + "name": "block_builder.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::BlockBuilder::empty() const", + "coverage": 1 + } + ] + }, + { + "name": "arena.cc", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::Arena::Arena()", + "coverage": 1 + }, + { + "name": "leveldb::Arena::~Arena()", + "coverage": 1 + }, + { + "name": "leveldb::Arena::AllocateFallback(unsigned long)", + "coverage": 1 + }, + { + "name": "leveldb::Arena::AllocateAligned(unsigned long)", + "coverage": 1 + }, + { + "name": "leveldb::Arena::AllocateNewBlock(unsigned long)", + "coverage": 1 + } + ] + }, + { + "name": "arena.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::Arena::MemoryUsage() const", + "coverage": 1 + }, + { + "name": "leveldb::Arena::Allocate(unsigned long)", + "coverage": 1 + } + ] + }, + { + "name": "block_builder.cc", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::BlockBuilder::BlockBuilder(leveldb::Options const*)", + "coverage": 1 + }, + { + "name": "leveldb::BlockBuilder::Reset()", + "coverage": 1 + }, + { + "name": "leveldb::BlockBuilder::CurrentSizeEstimate() const", + "coverage": 1 + }, + { + "name": "leveldb::BlockBuilder::Finish()", + "coverage": 1 + }, + { + "name": "leveldb::BlockBuilder::Add(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + } + ] + }, + { + "name": "port_posix_sse.cc", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::port::AcceleratedCRC32C(unsigned int, char const*, unsigned long)", + "coverage": 1 + } + ] + }, + { + "name": "crc32c.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::crc32c::Value(char const*, unsigned long)", + "coverage": 1 + }, + { + "name": "leveldb::crc32c::Mask(unsigned int)", + "coverage": 1 + }, + { + "name": "leveldb::crc32c::Unmask(unsigned int)", + "coverage": 1 + } + ] + }, + { + "name": "memtable.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::MemTable::Ref()", + "coverage": 1 + }, + { + "name": "leveldb::MemTable::Unref()", + "coverage": 1 + }, + { + "name": "leveldb::MemTable::KeyComparator::KeyComparator(leveldb::InternalKeyComparator const&)", + "coverage": 1 + } + ] + }, + { + "name": "table_builder.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::TableBuilder::ok() const", + "coverage": 1 + } + ] + }, + { + "name": "filter_policy.cc", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::FilterPolicy::~FilterPolicy()", + "coverage": 1 + } + ] + }, + { + "name": "table.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::Table::Table(leveldb::Table::Rep*)", + "coverage": 1 + } + ] + }, + { + "name": "slice.h", + "coverage": 1, + "type": "objc", + "functions": [ + { + "name": "leveldb::Slice::Slice()", + "coverage": 1 + }, + { + "name": "leveldb::Slice::Slice(char const*, unsigned long)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::Slice(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::Slice(char const*)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::data() const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::size() const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::empty() const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::operator[](unsigned long) const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::clear()", + "coverage": 1 + }, + { + "name": "leveldb::Slice::remove_prefix(unsigned long)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::ToString() const", + "coverage": 1 + }, + { + "name": "leveldb::Slice::starts_with(leveldb::Slice const&) const", + "coverage": 1 + }, + { + "name": "leveldb::operator==(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::operator!=(leveldb::Slice const&, leveldb::Slice const&)", + "coverage": 1 + }, + { + "name": "leveldb::Slice::compare(leveldb::Slice const&) const", + "coverage": 1 + } + ] + } + ] + }, + { + "name": "nanopb.framework", + "coverage": 0.15256797583081572, + "files": [ + { + "name": "pb_decode.c", + "coverage": 0, + "type": "objc", + "functions": [ + { + "name": "buf_read", + "coverage": 0 + }, + { + "name": "pb_read", + "coverage": 0 + }, + { + "name": "pb_readbyte", + "coverage": 0 + }, + { + "name": "pb_istream_from_buffer", + "coverage": 0 + }, + { + "name": "pb_decode_varint32_eof", + "coverage": 0 + }, + { + "name": "pb_decode_varint32", + "coverage": 0 + }, + { + "name": "pb_decode_varint", + "coverage": 0 + }, + { + "name": "pb_skip_varint", + "coverage": 0 + }, + { + "name": "pb_skip_string", + "coverage": 0 + }, + { + "name": "pb_decode_tag", + "coverage": 0 + }, + { + "name": "pb_skip_field", + "coverage": 0 + }, + { + "name": "read_raw_value", + "coverage": 0 + }, + { + "name": "pb_make_string_substream", + "coverage": 0 + }, + { + "name": "pb_close_string_substream", + "coverage": 0 + }, + { + "name": "decode_static_field", + "coverage": 0 + }, + { + "name": "allocate_field", + "coverage": 0 + }, + { + "name": "initialize_pointer_field", + "coverage": 0 + }, + { + "name": "decode_pointer_field", + "coverage": 0 + }, + { + "name": "decode_callback_field", + "coverage": 0 + }, + { + "name": "decode_field", + "coverage": 0 + }, + { + "name": "iter_from_extension", + "coverage": 0 + }, + { + "name": "default_extension_decoder", + "coverage": 0 + }, + { + "name": "decode_extension", + "coverage": 0 + }, + { + "name": "find_extension_field", + "coverage": 0 + }, + { + "name": "pb_field_set_to_default", + "coverage": 0 + }, + { + "name": "pb_message_set_to_defaults", + "coverage": 0 + }, + { + "name": "pb_decode_noinit", + "coverage": 0 + }, + { + "name": "pb_decode", + "coverage": 0 + }, + { + "name": "pb_decode_delimited_noinit", + "coverage": 0 + }, + { + "name": "pb_decode_delimited", + "coverage": 0 + }, + { + "name": "pb_decode_nullterminated", + "coverage": 0 + }, + { + "name": "pb_release_union_field", + "coverage": 0 + }, + { + "name": "pb_release_single_field", + "coverage": 0 + }, + { + "name": "pb_release", + "coverage": 0 + }, + { + "name": "pb_decode_svarint", + "coverage": 0 + }, + { + "name": "pb_decode_fixed32", + "coverage": 0 + }, + { + "name": "pb_decode_fixed64", + "coverage": 0 + }, + { + "name": "pb_dec_varint", + "coverage": 0 + }, + { + "name": "pb_dec_uvarint", + "coverage": 0 + }, + { + "name": "pb_dec_svarint", + "coverage": 0 + }, + { + "name": "pb_dec_fixed32", + "coverage": 0 + }, + { + "name": "pb_dec_fixed64", + "coverage": 0 + }, + { + "name": "pb_dec_bytes", + "coverage": 0 + }, + { + "name": "pb_dec_string", + "coverage": 0 + }, + { + "name": "pb_dec_submessage", + "coverage": 0 + }, + { + "name": "pb_dec_fixed_length_bytes", + "coverage": 0 + } + ] + }, + { + "name": "pb_encode.c", + "coverage": 0.3941717791411043, + "type": "objc", + "functions": [ + { + "name": "buf_write", + "coverage": 1 + }, + { + "name": "pb_ostream_from_buffer", + "coverage": 1 + }, + { + "name": "pb_write", + "coverage": 1 + }, + { + "name": "encode_array", + "coverage": 0 + }, + { + "name": "pb_check_proto3_default_value", + "coverage": 0 + }, + { + "name": "encode_basic_field", + "coverage": 0.39285714285714285 + }, + { + "name": "encode_callback_field", + "coverage": 1 + }, + { + "name": "encode_field", + "coverage": 0.9285714285714286 + }, + { + "name": "default_extension_encoder", + "coverage": 0 + }, + { + "name": "encode_extension_field", + "coverage": 0.4 + }, + { + "name": "pb_const_cast", + "coverage": 1 + }, + { + "name": "pb_encode", + "coverage": 0.8636363636363636 + }, + { + "name": "pb_encode_delimited", + "coverage": 0 + }, + { + "name": "pb_encode_nullterminated", + "coverage": 0 + }, + { + "name": "pb_get_encoded_size", + "coverage": 0 + }, + { + "name": "pb_encode_varint", + "coverage": 1 + }, + { + "name": "pb_encode_svarint", + "coverage": 0 + }, + { + "name": "pb_encode_fixed32", + "coverage": 0 + }, + { + "name": "pb_encode_fixed64", + "coverage": 0 + }, + { + "name": "pb_encode_tag", + "coverage": 1 + }, + { + "name": "pb_encode_tag_for_field", + "coverage": 0.8387096774193549 + }, + { + "name": "pb_encode_string", + "coverage": 0.8333333333333334 + }, + { + "name": "pb_encode_submessage", + "coverage": 0.8541666666666666 + }, + { + "name": "pb_enc_varint", + "coverage": 0.9375 + }, + { + "name": "pb_enc_uvarint", + "coverage": 0.625 + }, + { + "name": "pb_enc_svarint", + "coverage": 0 + }, + { + "name": "pb_enc_fixed64", + "coverage": 0 + }, + { + "name": "pb_enc_fixed32", + "coverage": 0 + }, + { + "name": "pb_enc_bytes", + "coverage": 0 + }, + { + "name": "pb_enc_string", + "coverage": 0 + }, + { + "name": "pb_enc_submessage", + "coverage": 1 + }, + { + "name": "pb_enc_fixed_length_bytes", + "coverage": 0 + } + ] + }, + { + "name": "pb_common.c", + "coverage": 0.5542168674698795, + "type": "objc", + "functions": [ + { + "name": "pb_field_iter_begin", + "coverage": 1 + }, + { + "name": "pb_field_iter_next", + "coverage": 0.6428571428571429 + }, + { + "name": "pb_field_iter_find", + "coverage": 0 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 688f343cda9..907e2a92f75 100644 --- a/README.md +++ b/README.md @@ -169,10 +169,9 @@ very grateful! We'd like to empower as many developers as we can to be able to participate in the Firebase community. ### macOS and tvOS -FirebaseAuth, FirebaseCore, FirebaseDatabase and FirebaseStorage now compile, run unit tests, and -work on macOS and tvOS, thanks to contributions from the community. There are a few tweaks needed, -like ensuring iOS-only, macOS-only, or tvOS-only code is correctly guarded with checks for -`TARGET_OS_IOS`, `TARGET_OS_OSX` and `TARGET_OS_TV`. +Thanks to contributions from the community, FirebaseAuth, FirebaseCore, FirebaseDatabase, +FirebaseFirestore, FirebaseFunctions and FirebaseStorage now compile, run unit tests, and work on +macOS and tvOS. FirebaseMessaging is available for tvOS. For tvOS, checkout the [Sample](Example/tvOSSample). @@ -181,10 +180,19 @@ actively developed primarily for iOS. While we can catch basic unit test issues may be some changes where the SDK no longer works as expected on macOS or tvOS. If you encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues). -For installation instructions, see [above](README.md#accessing-firebase-source-snapshots). +Note that the Firebase pod is not available for macOS and tvOS. -Note that the Firebase pod is not available for macOS and tvOS. Install a selection of the -`FirebaseAuth`, `FirebaseCore`, `FirebaseDatabase` and `FirebaseStorage` CocoaPods. +To install, add a subset of the following to the Podfile: + +``` +pod 'FirebaseAuth' +pod 'FirebaseCore' +pod 'FirebaseDatabase' +pod 'FirebaseFirestore' # Only iOS and macOS +pod 'FirebaseFunctions' +pod 'FirebaseMessaging' # Only iOS and tvOS +pod 'FirebaseStorage' +``` ## Roadmap diff --git a/ROADMAP.md b/ROADMAP.md index 1b6601a4bc8..07acdf00bf7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,21 +4,6 @@ The Firebase team plans to open source more of Firebase components. -## Continuous Integration - -* [Stabilize Travis](https://github.com/firebase/firebase-ios-sdk/issues/102) -* [Verify Objective-C style guide compliance](https://github.com/firebase/firebase-ios-sdk/issues/103) - -## Samples and Integration Tests - -Add more samples to better demonstrate the capabilities of Firebase and help -developers onboard. - -## Xcode 9 Workflow - -[Ensure Firebase open source development works well with Xcode 9's git and -GitHub features](https://github.com/firebase/firebase-ios-sdk/issues/101). - ## Other Check out the [issue list](https://github.com/firebase/firebase-ios-sdk/issues) diff --git a/Releases/Manifests/5.19.0.json b/Releases/Manifests/5.19.0.json new file mode 100644 index 00000000000..b8d333d612e --- /dev/null +++ b/Releases/Manifests/5.19.0.json @@ -0,0 +1,12 @@ +{ + "FirebaseAuth":"5.4.1", + "FirebaseCore":"5.4.0", + "FirebaseDatabase":"5.1.1", + "FirebaseDynamicLinks":"3.4.2", + "FirebaseFirestore":"1.1.0", + "FirebaseFunctions":"2.4.0", + "FirebaseInAppMessagingDisplay":"0.13.1", + "FirebaseInstanceID":"3.8.0", + "FirebaseMessaging":"3.4.0", + "FirebaseStorage":"3.1.1" +} diff --git a/Releases/Manifests/5.20.0.json b/Releases/Manifests/5.20.0.json new file mode 100644 index 00000000000..3afa84abc24 --- /dev/null +++ b/Releases/Manifests/5.20.0.json @@ -0,0 +1,8 @@ +{ + "FirebaseAuth":"5.4.2", + "FirebaseCore":"5.4.1", + "FirebaseDynamicLinks":"3.4.3", + "FirebaseFirestore":"1.2.0", + "FirebaseInstanceID":"3.8.1", + "FirebaseMessaging":"3.5.0" +} diff --git a/Releases/update-versions.py b/Releases/update-versions.py index 097828f3798..7dd00d3060a 100755 --- a/Releases/update-versions.py +++ b/Releases/update-versions.py @@ -114,24 +114,6 @@ def CreateReleaseBranch(release_branch, base_branch): release_branch)) -def UpdateFIROptions(git_root, version_data): - """Update version specifier in FIROptions.m. - - Args: - git_root: root of git checkout. - version_data: dictionary of versions to be updated. - """ - core_version = version_data['FirebaseCore'] - major, minor, patch = core_version.split('.') - path = os.path.join(git_root, 'Firebase', 'Core', 'FIROptions.m') - os.system("sed -E -i.bak 's/[[:digit:]]+\"[[:space:]]*\\/\\/ Major/" - "{}\" \\/\\/ Major/' {}".format(major, path)) - os.system("sed -E -i.bak 's/[[:digit:]]+\"[[:space:]]*\\/\\/ Minor/" - "{}\" \\/\\/ Minor/' {}".format(minor.zfill(2), path)) - os.system("sed -E -i.bak 's/[[:digit:]]+\"[[:space:]]*\\/\\/ Build/" - "{}\" \\/\\/ Build/' {}".format(patch.zfill(2), path)) - - def UpdatePodSpecs(git_root, version_data, firebase_version): """Update the podspecs with the right version. @@ -181,6 +163,7 @@ def UpdateTags(version_data, firebase_version, first=False): LogOrRun("git push --delete origin '{}'".format(firebase_version)) LogOrRun("git tag --delete '{}'".format(firebase_version)) LogOrRun("git tag '{}'".format(firebase_version)) + LogOrRun("git push origin '{}'".format(firebase_version)) for pod, version in version_data.items(): name = pod[len('Firebase'):] tag = '{}-{}'.format(name, version) @@ -222,9 +205,9 @@ def PushPodspecs(version_data): podspec = '{}.podspec'.format(pod) json = os.path.join(tmp_dir, '{}.json'.format(podspec)) - os.system('pod ipc spec {} > {}'.format(podspec, json)) - LogOrRun('pod repo push {} {}{}'.format(GetCpdcInternal(), json, - warnings_ok)) + LogOrRun('pod ipc spec {} > {}'.format(podspec, json)) + LogOrRun('pod repo push --skip-tests {} {}{}'.format(GetCpdcInternal(), + json, warnings_ok)) os.system('rm -rf {}'.format(tmp_dir)) @@ -253,7 +236,6 @@ def UpdateVersions(): release_branch = 'release-{}'.format(args.version) CreateReleaseBranch(release_branch, args.base_branch) - UpdateFIROptions(git_root, version_data) UpdatePodSpecs(git_root, version_data, args.version) UpdatePodfiles(git_root, args.version) diff --git a/Rome.md b/Rome.md index 47bbba9f536..0b08526b046 100644 --- a/Rome.md +++ b/Rome.md @@ -52,8 +52,8 @@ Then do the following steps: `file Rome/*/* | grep universal | grep dynamic` 1. Drag each dynamic framework to the "Embed Frameworks" section on the Xcode Build Target's "General" page. -1. If you're using FirebaseML, FirebaseInAppMessaging, FirebaseFirestore, or - FirebaseInvites, find +1. If you're using FirebaseML, FirebaseInAppMessaging, or FirebaseFirestore, + find the resources to the project: `ls -ld Pods/*/Resources/*`. More details on this below. 1. Drag all of those resources into the Project Navigator, just @@ -85,10 +85,6 @@ Then do the following steps: - ./Rome/GRPCClient.framework/gRPCCertificates.bundle - For InAppMessagingDisplay: - ./Rome/FirebaseInAppMessagingDisplay.framework/InAppMessagingDisplayResources.bundle - - For Invites: - - ./Pods/FirebaseInvites/Resources/GINInviteResources.bundle - - ./Pods/FirebaseInvites/Resources/GPPACLPickerResources.bundle - - ./Pods/GoogleSignIn/Resources/GoogleSignIn.bundle - For FirebaseMLVisionFaceModel: - ./Pods/GoogleMobileVision/FaceDetector/Resources/GoogleMVFaceDetectorResources - For FirebaseMLVisionTextModel: diff --git a/SymbolCollisionTest/Podfile b/SymbolCollisionTest/Podfile index 5252216278c..af21941f159 100644 --- a/SymbolCollisionTest/Podfile +++ b/SymbolCollisionTest/Podfile @@ -6,11 +6,10 @@ target 'SymbolCollisionTest' do # use_frameworks! # Firebase Pods - pod 'Firebase', '5.18.0' + pod 'Firebase', '5.20.1' pod 'FirebaseAnalytics' pod 'FirebaseAuth' pod 'FirebaseCore' - pod 'FirebaseCrash' pod 'FirebaseDatabase' pod 'FirebaseDynamicLinks' pod 'FirebaseFirestore' diff --git a/ZipBuilder/FirebaseSDKs.proto b/ZipBuilder/FirebaseSDKs.proto index b06d0a38877..817b6165909 100644 --- a/ZipBuilder/FirebaseSDKs.proto +++ b/ZipBuilder/FirebaseSDKs.proto @@ -32,6 +32,13 @@ message SDK { // Whether or not to strip the i386 architecture from the build. bool strip_i386 = 8; + + // List of build targets. For internal use only. + repeated string build_target = 9; + + // Whether or not to strip both the i386 and armv7 architectures from the + // build. All SDKs that use this flag are built internally so this should be ignored. + bool strip_32bits = 10; } // Any extra build flags needed to build the SDK. For internal use only. diff --git a/ZipBuilder/README.md b/ZipBuilder/README.md index 8759b101857..54586f73e46 100644 --- a/ZipBuilder/README.md +++ b/ZipBuilder/README.md @@ -1,7 +1,74 @@ # Firebase Zip File Builder This project builds the Firebase iOS Zip file for distribution. -More instructions to come. + +## Overview + +This is a small Swift Package Manager project that allows users to package a Firebase iOS Zip file. With no launch +arguments, it will use the most recent public versions of all SDKs included in the zip file. + +It was designed to fail fast with an explanation of what went wrong, so you can fix issues or dig in without having to dig +too deep into the code. + +## Requirements + +In order to build the Zip file, you will need: + +- Xcode 10.1 +- CocoaPods +- An internet connection to fetch CocoaPods + +## Running the Tool + +You can run the tool with `swift run ZipBuilder [ARGS]` or generate an Xcode project with +`swift package generate-xcodeproj` and run within Xcode. + +In the near future, releases will be built via a builder server instead of on the release engineer's machine, making these +instructions more of a reference to understand what's going on instead of how to build it yourself. + +## Launch Arguments + +See `main.swift` and the `LaunchArgs` struct for information on specific launch arguments. + +You can pass in launch arguments with Xcode by clicking "ZipBuilder" beside the Run/Stop buttons, clicking "Edit +Scheme" and adding them in the "Arguments Passed On Launch" section. + +### Common Arguments + +These arguments assume you're running the command from the `ZipBuilder` directory. + +**Required** arguments: +- `-templateDir $(pwd)/Template` + - This should always be the same. +- `-coreDiagnosticsDir ` + - Needed to overwrite the existing Core Diagnostics framework. + +Optional comon arguments: +- `-updatePodRepo false` + - This is for speedups when `pod repo update` has already been run recently. + +For release engineers (Googlers packaging an upcoming Firebase release) these commands should also be used: +- `-customSpecRepos sso://cpdc-internal/firebase` + - This pulls the latest podspecs from the CocoaPods staging area. +- `-releasingSDKs ` and +- `-existingVersions ` + - Validates the version numbers fetched from CocoaPods staging against the expected released versions from these + textprotos. + +Putting them all together, here's a common command to build a releaseable Zip file: + +``` +swift run ZipBuilder -templateDir $(pwd)/Template -updatePodRepo false \ +-coreDiagnosticsDir /private/tmp/tmpUqBxKN/FirebaseCoreDiagnostics.framework \ +-releasingSDKs \ +-existingVersions \ +-customSpecRepos sso://cpdc-internal/firebase +``` + +## Debugging + +You can generate an Xcode project for the tool by running `swift package generate-xcodeproj` in this directory. +See the above instructions for adding Launch Arguments to the Xcode build. ## Priorities diff --git a/ZipBuilder/Sources/ZipBuilder/CocoaPodUtils.swift b/ZipBuilder/Sources/ZipBuilder/CocoaPodUtils.swift index c35c1e60820..67fdc56f2e0 100644 --- a/ZipBuilder/Sources/ZipBuilder/CocoaPodUtils.swift +++ b/ZipBuilder/Sources/ZipBuilder/CocoaPodUtils.swift @@ -50,7 +50,20 @@ public enum CocoaPodUtils { } } - /// Execute the `pod cache list` command to get the Pods currently cached on your machine. + /// Executes the `pod cache clean --all` command to remove any cached CocoaPods. + public static func cleanPodCache() { + let result = Shell.executeCommandFromScript("pod cache clean --all", outputToConsole: false) + switch result { + case let .error(code): + fatalError("Could not clean the pod cache, the command exited with \(code). Try running the" + + "command in Terminal to see what's wrong.") + case .success: + // No need to do anything else, continue on. + print("Successfully cleaned pod cache.") + return + } + } + /// Executes the `pod cache list` command to get the Pods curerntly cached on your machine. /// /// - Parameter dir: The directory containing all installed pods. @@ -133,7 +146,7 @@ public enum CocoaPodUtils { } // Run pod install on the directory that contains the Podfile and blank Xcode project. - let result = Shell.executeCommandFromScript("pod install", workingDir: directory) + let result = Shell.executeCommandFromScript("pod _1.5.3_ install", workingDir: directory) switch result { case let .error(code, output): fatalError(""" diff --git a/ZipBuilder/Sources/ZipBuilder/FirebaseSDKs.pb.swift b/ZipBuilder/Sources/ZipBuilder/FirebaseSDKs.pb.swift index c31bde14206..66b9054b458 100644 --- a/ZipBuilder/Sources/ZipBuilder/FirebaseSDKs.pb.swift +++ b/ZipBuilder/Sources/ZipBuilder/FirebaseSDKs.pb.swift @@ -106,6 +106,19 @@ struct ZipBuilder_SDK { set {_uniqueStorage()._stripI386 = newValue} } + /// List of build targets. For internal use only. + var buildTarget: [String] { + get {return _storage._buildTarget} + set {_uniqueStorage()._buildTarget = newValue} + } + + /// Whether or not to strip both the i386 and armv7 architectures from the + /// build. For internal use only. + var strip32Bits: Bool { + get {return _storage._strip32Bits} + set {_uniqueStorage()._strip32Bits = newValue} + } + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -171,6 +184,8 @@ extension ZipBuilder_SDK: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement 6: .standard(proto: "nightly_mpm_pattern"), 7: .standard(proto: "open_source"), 8: .standard(proto: "strip_i386"), + 9: .standard(proto: "build_target"), + 10: .standard(proto: "strip_32bits"), ] fileprivate class _StorageClass { @@ -182,6 +197,8 @@ extension ZipBuilder_SDK: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement var _nightlyMpmPattern: [String] = [] var _openSource: Bool = false var _stripI386: Bool = false + var _buildTarget: [String] = [] + var _strip32Bits: Bool = false static let defaultInstance = _StorageClass() @@ -196,6 +213,8 @@ extension ZipBuilder_SDK: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement _nightlyMpmPattern = source._nightlyMpmPattern _openSource = source._openSource _stripI386 = source._stripI386 + _buildTarget = source._buildTarget + _strip32Bits = source._strip32Bits } } @@ -219,6 +238,8 @@ extension ZipBuilder_SDK: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement case 6: try decoder.decodeRepeatedStringField(value: &_storage._nightlyMpmPattern) case 7: try decoder.decodeSingularBoolField(value: &_storage._openSource) case 8: try decoder.decodeSingularBoolField(value: &_storage._stripI386) + case 9: try decoder.decodeRepeatedStringField(value: &_storage._buildTarget) + case 10: try decoder.decodeSingularBoolField(value: &_storage._strip32Bits) default: break } } @@ -251,6 +272,12 @@ extension ZipBuilder_SDK: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if _storage._stripI386 != false { try visitor.visitSingularBoolField(value: _storage._stripI386, fieldNumber: 8) } + if !_storage._buildTarget.isEmpty { + try visitor.visitRepeatedStringField(value: _storage._buildTarget, fieldNumber: 9) + } + if _storage._strip32Bits != false { + try visitor.visitSingularBoolField(value: _storage._strip32Bits, fieldNumber: 10) + } } try unknownFields.traverse(visitor: &visitor) } @@ -268,6 +295,8 @@ extension ZipBuilder_SDK: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if _storage._nightlyMpmPattern != rhs_storage._nightlyMpmPattern {return false} if _storage._openSource != rhs_storage._openSource {return false} if _storage._stripI386 != rhs_storage._stripI386 {return false} + if _storage._buildTarget != rhs_storage._buildTarget {return false} + if _storage._strip32Bits != rhs_storage._strip32Bits {return false} return true } if !storagesAreEqual {return false} diff --git a/ZipBuilder/Sources/ZipBuilder/FrameworkBuilder.swift b/ZipBuilder/Sources/ZipBuilder/FrameworkBuilder.swift index d6de936c2e9..dc42bde77ef 100755 --- a/ZipBuilder/Sources/ZipBuilder/FrameworkBuilder.swift +++ b/ZipBuilder/Sources/ZipBuilder/FrameworkBuilder.swift @@ -83,24 +83,26 @@ struct FrameworkBuilder { cacheEnabled: Bool = false) -> (framework: URL, resources: URL) { print("Building \(podName)") +// Cache is temporarily disabled due to pod cache list issues. // Get the CocoaPods cache to see if we can pull from any frameworks already built. - let podsCache = CocoaPodUtils.listPodCache(inDir: projectDir) - - guard let cachedVersions = podsCache[podName] else { - fatalError("Cannot find a pod cache for framework \(podName).") - } - - guard let podInfo = cachedVersions[version] else { - fatalError(""" - Cannot find a pod cache for framework \(podName) at version \(version). - Something could be wrong with your CocoaPods cache - try running the following: - - pod cache clean '\(podName)' --all - """) - } - - // TODO: Figure out if we need the MD5 at all. - let md5 = Shell.calculateMD5(for: podInfo.installedLocation) +// let podsCache = CocoaPodUtils.listPodCache(inDir: projectDir) +// +// guard let cachedVersions = podsCache[podName] else { +// fatalError("Cannot find a pod cache for framework \(podName).") +// } +// +// guard let podInfo = cachedVersions[version] else { +// fatalError(""" +// Cannot find a pod cache for framework \(podName) at version \(version). +// Something could be wrong with your CocoaPods cache - try running the following: +// +// pod cache clean '\(podName)' --all +// """) +// } +// +// // TODO: Figure out if we need the MD5 at all. + let md5 = podName +// let md5 = Shell.calculateMD5(for: podInfo.installedLocation) // Get (or create) the cache directory for storing built frameworks. let fileManager = FileManager.default diff --git a/ZipBuilder/Sources/ZipBuilder/ShellUtils.swift b/ZipBuilder/Sources/ZipBuilder/ShellUtils.swift index dd6f8820d65..59b6a256170 100644 --- a/ZipBuilder/Sources/ZipBuilder/ShellUtils.swift +++ b/ZipBuilder/Sources/ZipBuilder/ShellUtils.swift @@ -54,9 +54,10 @@ extension Shell { scriptPath = tempScriptsDir.appendingPathComponent("wrapper.sh") // Write the temporary script contents to the script's path. CocoaPods complains when LANG - // isn't set in the environment, so explicitly set it here. + // isn't set in the environment, so explicitly set it here. The `/usr/local/git/current/bin` + // is to allow the `sso` protocol if it's there. let contents = """ - export PATH="/usr/local/bin:$PATH" + export PATH="/usr/local/bin:/usr/local/git/current/bin:$PATH" export LANG="en_US.UTF-8" source ~/.bash_profile \(command) diff --git a/ZipBuilder/Sources/ZipBuilder/Subspec.swift b/ZipBuilder/Sources/ZipBuilder/Subspec.swift index 16eebee0400..d07a9007bc4 100644 --- a/ZipBuilder/Sources/ZipBuilder/Subspec.swift +++ b/ZipBuilder/Sources/ZipBuilder/Subspec.swift @@ -24,18 +24,17 @@ public enum Subspec: String, CaseIterable { case analytics = "Analytics" case auth = "Auth" case core = "Core" - case crash = "Crash" case database = "Database" case dynamicLinks = "DynamicLinks" case firestore = "Firestore" case functions = "Functions" case inAppMessaging = "InAppMessaging" case inAppMessagingDisplay = "InAppMessagingDisplay" - case invites = "Invites" case messaging = "Messaging" case mlModelInterpreter = "MLModelInterpreter" case mlNaturalLanguage = "MLNaturalLanguage" case mlNLLanguageID = "MLNLLanguageID" + case mlNLSmartReply = "MLNLSmartReply" case mlVision = "MLVision" case mlVisionBarcodeModel = "MLVisionBarcodeModel" case mlVisionFaceModel = "MLVisionFaceModel" diff --git a/ZipBuilder/Sources/ZipBuilder/ZipBuilder.swift b/ZipBuilder/Sources/ZipBuilder/ZipBuilder.swift index 0aba05357d7..82667f03d89 100644 --- a/ZipBuilder/Sources/ZipBuilder/ZipBuilder.swift +++ b/ZipBuilder/Sources/ZipBuilder/ZipBuilder.swift @@ -33,9 +33,6 @@ private struct Constants { public static let modulemap = "module.modulemap" public static let notices = "NOTICES" - // Directory containing extra FirebaseCrash scripts. - public static let crashDir = "Crash" - /// All required files for distribution. Note: the readmeTemplate is also needed for /// distribution but is copied separately since it's modified. public static let requiredFilesForDistribution: [String] = [firebaseHeader, modulemap, notices] @@ -182,6 +179,10 @@ struct ZipBuilder { // builds to just install a subset: `[.core, .analytics, .storage, .firestore]` for example. let subspecsToInstall = Subspec.allCases + // Remove CocoaPods cache so the build gets updates after a version is rebuilt during the + // release process. + CocoaPodUtils.cleanPodCache() + // We need to install all the subpsecs in order to get every single framework that we'll need // for the zip file. We can't install each one individually since some pods depend on different // subspecs from the same pod (ex: GoogleUtilities, GoogleToolboxForMac, etc). All of the code @@ -205,12 +206,33 @@ struct ZipBuilder { // Create an array that has the Pod name as the key and the array of frameworks needed - this // will be used as the source of truth for all frameworks to be copied in each product's // directory. - let frameworks = filesToInstall.mapValues { $0.frameworks } + var frameworks = filesToInstall.mapValues { $0.frameworks } for (framework, paths) in frameworks { print("Frameworks for pod: \(framework) were compiled at \(paths)") } - // TODO: Overwrite the `CoreDiagnostics.framework` in the generated framework. + // Overwrite the `FirebaseCoreDiagnostics.framework` in the `FirebaseAnalytics` folder. This is + // needed because it was compiled specifically with the `ZIP` bit enabled, helping us understand + // the distribution of CocoaPods vs Zip file integrations. + if subspecsToInstall.contains(.analytics) { + let overriddenAnalytics: [URL] = { + guard let currentFrameworks = frameworks["FirebaseAnalytics"] else { + fatalError("Attempted to replace CoreDiagnostics framework but the FirebaseAnalytics " + + "directory does not exist. Existing directories: \(frameworks.keys)") + } + + // Filter out any CoreDiagnostics directories from the frameworks to install. There should + // only be one. + let withoutDiagnostics: [URL] = currentFrameworks.filter { url in + url.lastPathComponent != "FirebaseCoreDiagnostics.framework" + } + + return withoutDiagnostics + [paths.coreDiagnosticsDir] + }() + + // Set the newly required framework paths for Analytics. + frameworks["FirebaseAnalytics"] = overriddenAnalytics + } // TODO: The folder heirarchy should change in Firebase 6. // Time to assemble the folder structure of the Zip file. In order to get the frameworks @@ -335,24 +357,6 @@ struct ZipBuilder { fatalError("Could not write README to Zip directory: \(error)") } - // TODO: Remove this manual copy once FirebaseCrash is removed from the Zip file. - // Copy over the Crash scripts, if Crash should be installed - if subspecsToInstall.contains(.crash) { - do { - let crashDir = paths.templateDir.appendingPathComponent(Constants.ProjectPath.crashDir) - let crashFiles = try FileManager.default.contentsOfDirectory(at: crashDir, - includingPropertiesForKeys: nil, - options: []) - let crashZipDir = zipDir.appendingPathComponent("Crash") - for file in crashFiles { - let destination = crashZipDir.appendingPathComponent(file.lastPathComponent) - try FileManager.default.copyItem(at: file, to: destination) - } - } catch { - fatalError("Could not copy extra Crash tools: \(error)") - } - } - print("Contents of the Zip file were assembled at: \(zipDir)") return zipDir } @@ -735,6 +739,17 @@ struct ZipBuilder { fatalError("Cannot move Resource bundles for \(pod.name): \(error)") } + // Smart Reply packages Resources separately from other MLKit subspecs. + if pod.name == "FirebaseMLNLSmartReply" { + do { + resourceBundles = try ResourcesManager.createBundleForFoldersInResourcesDirs( + containedIn: pod.installedLocation, destinationDir: podResourceDir + ) + } catch { + fatalError("Could not generate Resource bundles for \(pod.name): \(error)") + } + } + // Special case for MLKit *Model subspecs, explicitly copy directories from // GoogleMobileVision. This should be fixed in the future to pull all compiled resources // from Xcode's build directory. diff --git a/ZipBuilder/Template/Crash/batch-upload b/ZipBuilder/Template/Crash/batch-upload deleted file mode 100755 index 053a3ee7fe3..00000000000 --- a/ZipBuilder/Template/Crash/batch-upload +++ /dev/null @@ -1,416 +0,0 @@ -#!/bin/bash - -usage () { - echo >&2 "usage: ${0##*/} [-hv] [-p google-service] [-i info] service-account-file {mach-o file|uuid} ..." -} - -help () { - usage - cat >&2 </dev/null)"}" -fi - -var_check FCR_PROD_VERS FCR_BUNDLE_ID - -ERROR=$'environment variable empty or unset\n\nExplicitly add to environment or set GoogleService-Info.plist (-p)\nand Info.plist (-i) flags to extract values from the files.\n\nTry "'"$0"' -h" for details.' - -: "${FIREBASE_API_KEY:?"${ERROR}"}" "${FIREBASE_APP_ID:?"${ERROR}"}" -: "${FCR_PROD_VERS:?"${ERROR}"}" "${FCR_BUNDLE_ID:?"${ERROR}"}" - -# Extract key from legacy cache. - -if [[ ! "${SERVICE_ACCOUNT_FILE}" ]]; then - xcwarning "Running extract-keys on desktop." - EXTRACT_KEYS="$(script_dir)/extract-keys" - (cd "${HOME}/Desktop"; "${EXTRACT_KEYS}") || exit $? - SERVICE_ACCOUNT_FILE="${HOME}/Desktop/${FIREBASE_APP_ID}.json" - xcdebug "Using ${SERVICE_ACCOUNT_FILE} as account file. Please move this and all other extracted keys to a safe place." -fi - -if [[ ! -f "${SERVICE_ACCOUNT_FILE}" ]]; then - echo >&2 "Unable to find service account file." - echo >&2 - usage - exit 2 -fi - -# usage: extract_symbols_and_upload *dwarf-file* *arch* *exe-file* -# -# Do NOT use the dSYM bundle path. While it may work on occasion, it -# is not guaranteed to do so; the full path to the DWARF companion -# file will always work. (Discovered by Kerem Erkan.) -# -# If the executable is empty, use the DWARF companion file as a proxy -# for the executable. -extract_symbols_and_upload () { - local DWARF_COMPANION="$1" ARCH="$2" EXECUTABLE="$3" - - if [[ ! "${EXECUTABLE}" ]]; then - xcdebug "No executable; using ${DWARF_COMPANION} as symbol source." - - EXECUTABLE="${DWARF_COMPANION}" - unset DWARF_COMPANION - fi - - [[ "${EXECUTABLE}" ]] || return 1 - - if [[ -x "${SWIFT_DEMANGLE:=$(xcrun --find swift-demangle 2>/dev/null)}" ]]; - then - SWIFT_DEMANGLE_COMMAND="${SWIFT_DEMANGLE} -simplified" - else - SWIFT_DEMANGLE_COMMAND=/bin/cat - fi - fcr_mktemp SYMBOL_FILE - - "${DUMP_SYMS:="$(script_dir)/dump_syms"}" -a "${ARCH}" ${DWARF_COMPANION:+-g "${DWARF_COMPANION}"} "${EXECUTABLE}" | ${SWIFT_DEMANGLE_COMMAND} >|"${SYMBOL_FILE}" || return $? - - fcr_upload_files "${SYMBOL_FILE}" || return $? -} - -# usage: is_executable *path* -# -# Check to see if the file is an executable or a dSYM bundle -is_executable () { - [[ -f "$1" || ( -d "$1" && "${1%/}" == *.dSYM ) ]] -} - -# usage: is_uuid *string* -# -# Verify that the argument is a UUID. -is_uuid () { - [[ "$1" =~ ^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$ ]] -} - -# usage: set_uuids_archs *mach-o-file* -# -# side effect: appends to UUIDS, ARCHS -# -# Extract the uuid and architecture information from the given Mach-O -# file and append the information to the UUIDS and ARCHS arrays. -set_uuids_archs () { - eval "$(dwarfdump --uuid "$1" | awk '/^UUID:/ { print "UUIDS+=(" $2 "); ARCHS+=" $3 }')" -} - -# usage: mdls_to_bash -# -# Convert the output of mdls to a string consumable by bash. mdls -# outputs string arrays as quoted strings separated by commas, and -# Unicode characters as '\Uxxxx'. -# -# Note: this is sensitive to the current locale. If the locale is not -# UTF-8, then wide-character warnings will result if the strings -# contain non-ASCII characters. This is actually a desired behavior, -# because bash has issues with non-Unicode encodings for file names. -# (The macOS default is to have UTF-8 enabled, so this should not be a -# problem for the majority of use cases.) -mdls_to_bash () { - perl -C -ple 's/,$//; s/\\U(....)/chr hex $1/ge' -} - -for EXE; do - if is_executable "${EXE}"; then - xcdebug "Assuming ${EXE} is an executable or dSYM bundle." - - # Import architecture UUID information - UUIDS=() ARCHS=() - set_uuids_archs "${EXE}" - - for I in "${!UUIDS[@]}"; do - xcdebug "Found ${UUIDS[$I]} for ${ARCHS[$I]} in ${EXE}" - done - - if ((${#UUIDS[*]} == 0)); then - xcwarning "${EXE} exists, but has no architecture information." - continue - fi - - if [[ "${EXE}" = *.dSYM ]]; then - xcdebug "Removing dSYM bundle as executable target." - unset EXE - fi - - elif is_uuid "${EXE}"; then - xcdebug "${EXE} looks like a UUID to me." - UUIDS=("${EXE}"); unset EXE - - else - xcwarning "${EXE}: not an executable, bundle, or UUID." - continue - fi - - BUNDLES=() - - for UUID in "${UUIDS[@]}"; do - xcdebug "Searching for ${UUID} ..." - - QUERY_UUID="com_apple_xcode_dsym_uuids == '${UUID}'" - QUERY_TYPE="kMDItemContentType == 'com.apple.xcode.dsym' || kMDItemContentType == 'com.apple.xcode.archive'" - QUERY="(${QUERY_UUID}) && (${QUERY_TYPE})" - - if ((VERBOSE > 1)); then - xcnote "Passing query \"${QUERY}\" to mdfind." - fi - - MD_FIND_RESULT=() - - eval "$(mdfind "${QUERY}" -0 | xargs -0 perl -le 'print "MD_FIND_RESULT+=(\Q$_\E)" for @ARGV')" - - xcdebug "mdfind returned (${MD_FIND_RESULT[*]})" - - # BUNDLES should contain no duplicates. - for I in "${!MD_FIND_RESULT[@]}"; do - for BUNDLE in "${BUNDLES[@]}"; do - if [[ "${MD_FIND_RESULT[$I]}" == "$BUNDLE" ]]; then - unset "MD_FIND_RESULT[$I]" - fi - done - done - - BUNDLES+=("${MD_FIND_RESULT[@]}") - done - - if [[ ${#BUNDLES[@]} == 0 && ${#ARCHS[@]} == 0 ]]; then - xcwarning "No executable or bundle found for ${UUIDS[*]}." - xcnote "Try passing in the executable itself instead of a UUID." - continue - fi - - xcdebug "BUNDLES = (${BUNDLES[*]})" - - if [[ ${#BUNDLES[@]} == 0 ]]; then - xcdebug "No dSYM bundle found." - - # The dSYM has to be on a normal volume (not temporary). It - # can, however, be shared among multiple executables. - if [[ ! "${SCRATCH_BUNDLE}" ]]; then - SCRATCH_BUNDLE="${HOME}/com.google.BatchUploadScratchFile.dSYM" - FCR_TEMPORARY_FILES+=("${SCRATCH_BUNDLE}") - fi - - xcdebug "Creating one in ${SCRATCH_BUNDLE}" - - BUNDLES=("${SCRATCH_BUNDLE}") - - # Create the dSYM bundle. This may produce an empty dSYM - # bundle if the executable has no debugging information. - xcrun dsymutil -o "${BUNDLES[0]}" "${EXE}"; STATUS=$? - - if ((STATUS)); then - xcwarning "Command dsymutil failed with exit code ${STATUS}." - continue - fi - - # Import the dSYM bundle. There is a momentary delay between - # creating the bundle and having it indexed; explicitly - # importing guarantees the mds database is up-to-date when we - # ask it for information about UUIDs and paths. - mdimport "${SCRATCH_BUNDLE}"; STATUS=$? - - if ((STATUS)); then - xcwarning "Command mdimport failed with exit code ${STATUS}." - continue - fi - fi - - SEEN_ARCH=() SEEN_PATH=() - - for BUNDLE in "${BUNDLES[@]}"; do - typeset -a BNDL_UUIDS BNDL_PATHS # keeps ShellLint happy - - eval "BNDL_UUIDS=$(mdls -raw -name com_apple_xcode_dsym_uuids "${BUNDLE}" | mdls_to_bash)" - eval "BNDL_PATHS=$(mdls -raw -name com_apple_xcode_dsym_paths "${BUNDLE}" | mdls_to_bash)" - - # Neither of these SHOULD occur, but curious things happen out - # in the field. - if ((${#BNDL_UUIDS[@]} != ${#BNDL_PATHS[@]})); then - xcwarning "${BUNDLE}: Malformed dSYM bundle." - continue - elif ((${#BNDL_UUIDS[@]} == 0)); then - xcwarning "${BUNDLE}: No DWARF information." - continue - fi - - # If no executable was specified, then the UUIDS and ARCHS - # arrays are empty. Populate them with information from the - # bundle. - if [[ ! "${EXE}" ]]; then - # The final UUIDS setting will be the intersection of the - # discovered set and the originally specified UUIDS. This - # is to prevent uploading potentially private information. - SOUGHT_UUIDS=("${UUIDS[@]}") - - UUIDS=() ARCHS=() - for BNDL_PATH in "${BNDL_PATHS[@]}"; do - set_uuids_archs "${BUNDLE}/${BNDL_PATH}" - done - - if ((${#SOUGHT_UUIDS[@]})); then - for I in "${!UUIDS[@]}"; do - for UUID in "${SOUGHT_UUIDS[@]}"; do - if [[ "${UUIDS[$I]}" == "${UUID}" ]]; then - continue 2 - fi - done - - # This is not the DWARF you are looking for... - xcdebug "Rejecting ${UUIDS[$I]} (${ARCHS[$I]}) as candidate DWARF file." - unset "UUIDS[$I]" "ARCHS[$I]" - done - fi - - unset SOUGHT_UUIDS - fi - - for I in "${!BNDL_UUIDS[@]}"; do - # See comment on extract_symbols_and_upload for why the - # full path to the companion file is required. - - BNDL_UUID="${BNDL_UUIDS[$I]}" DWARF_COMPANION="${BUNDLE}/${BNDL_PATHS[$I]}" - - for J in "${!ARCHS[@]}"; do - # A dSYM bundle can contain multiple architectures for - # multiple applications. Make sure we get the right - # one. - if [[ "${BNDL_UUID}" == "${UUIDS[$J]}" ]]; then - ARCH="${ARCHS[$J]}" - break - fi - done - - if [[ ! "${ARCH}" ]]; then - # This is not an error: it is legal for a dSYM bundle - # to contain debugging information for multiple - # executables (such as a framework with multiple - # subframeworks). Just ignore it. - xcdebug "No matching information found in ${DWARF_COMPANION} with UUID ${BNDL_UUID}." - continue - fi - - xcdebug "Found ${UUID} for ${ARCH} in ${DWARF_COMPANION}" - - # Have we already uploaded this file? - for J in "${!SEEN_ARCH[@]}"; do - if [[ "${ARCH}" == "${SEEN_ARCH[$J]}" ]] && cmp -s "${DWARF_COMPANION}" "${SEEN_PATH[$J]}"; then - xcdebug "${DWARF_COMPANION}: copy of ${SEEN_PATH[$J]}; no need to upload." - continue 2 - fi - done - - if [[ -f "${DWARF_COMPANION}" ]]; then - extract_symbols_and_upload "${DWARF_COMPANION}" "${ARCH}" "${EXE}" || exit $? - SEEN_ARCH+=("${ARCH}") SEEN_PATH+=("${DWARF_COMPANION}") - fi - done - done -done - -# For debugging odd cases. -if "${KEEP_TEMPORARIES}"; then - FCR_TEMPORARY_FILES=() -fi - -echo "Done." diff --git a/ZipBuilder/Template/Crash/dump_syms b/ZipBuilder/Template/Crash/dump_syms deleted file mode 100755 index 8d0ef781cc8..00000000000 Binary files a/ZipBuilder/Template/Crash/dump_syms and /dev/null differ diff --git a/ZipBuilder/Template/Crash/extract-keys b/ZipBuilder/Template/Crash/extract-keys deleted file mode 100755 index 0da57003f9a..00000000000 --- a/ZipBuilder/Template/Crash/extract-keys +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -PLIST="${HOME}/Library/Preferences/com.google.SymbolUpload.plist" - -[[ -f $PLIST ]] || exit - -defaults read com.google.SymbolUpload | -perl -nle '/"(app_\d+_\d+_ios_.*)"/ and print $1' | -while read KEY; do - APP_ID="${KEY#app_}"; APP_ID="${APP_ID//_/:}" - plutil -extract "${KEY}" json -o "${APP_ID}.json" "${PLIST}" -done diff --git a/ZipBuilder/Template/Crash/upload-sym b/ZipBuilder/Template/Crash/upload-sym deleted file mode 100755 index 1f8327dcebf..00000000000 --- a/ZipBuilder/Template/Crash/upload-sym +++ /dev/null @@ -1,273 +0,0 @@ -#!/bin/bash - -usage () { - echo >&2 "usage: $0 [-h] [-v] [-w|-e] service-account-file" -} - -help () { - usage - - cat >&2 <&2 "Either -w or -e may be specified, but not both." - echo >&2 - usage - exit 2 -fi - -SERVICE_ACCOUNT_FILE="$1"; shift - -if (($#)); then - echo >&2 "Unexpected argument '$1'" - echo >&2 - usage - exit 2 -fi - -export PATH=/bin:/usr/bin # play it safe - -# Load common utility routines. - -. "$(dirname "$0")/upload-sym-util.bash" - -# Make the error output Xcode-friendly. - -# This is a bit of Bash voodoo that cries for an explanation and is -# horribly underdocumented on-line. The construct '>(...)' starts a -# subprocess with its stdin connected to a pipe. After starting the -# subprocess, the parser replaces the construct with the NAME of the -# writable end of the pipe as a named file descriptor '/dev/fd/XX', -# then reevaluates the line. So, after the subprocess is started -# (which filters stdin and outputs to stderr [not stdout]), the line -# "exec 2> /dev/fd/XX" is evaluated. This redirects the main -# process's stderr to the given file descriptor. -# -# The end result is that anything sent to stderr of the form: -# file.in: line 47: blah blah -# is replaced with -# file.in:47: error: blah blah -# which Xcode will detect and emphasize in the formatted output. - -exec 2> >(sed -e 's/: line \([0-9]*\):/:\1: error:/' >&2) - -# Be long-winded about problems. The user may not understand how this -# script works or what prerequisites it has. If the user sees this, -# it is likely that they are executing the script outside of an Xcode -# build. - -ERRMSG=$'Value missing\n\nThis script must be executed as part of an Xcode build stage to have the\nproper environment variables set.' - -# Locate Xcode-generated files. - -: "${TARGET_BUILD_DIR:?"${ERRMSG}"}" -: "${FULL_PRODUCT_NAME:?"${ERRMSG}"}" - -DSYM_BUNDLE="${DWARF_DSYM_FOLDER_PATH?"${ERRMSG}"}/${DWARF_DSYM_FILE_NAME?"${ERRMSG}"}" -[[ -e "${DSYM_BUNDLE}" ]] || unset DSYM_BUNDLE - -EXECUTABLE="${TARGET_BUILD_DIR?"${ERRMSG}"}/${EXECUTABLE_PATH?"${ERRMSG}"}" - -# Locate dump_syms utility. - -if ! [[ -f "${FCR_DUMP_SYMS:=$(script_dir)/dump_syms}" && -x "${FCR_DUMP_SYMS}" ]]; then - xcerror "Cannot find dump_syms." - xcnote "It should have been installed with the Cocoapod. The location of dump_syms can be explicitly set using the environment variable FCR_DUMP_SYMS if you are using a non-standard install." - - exit 2 -fi - -if [[ ! "${FIREBASE_API_KEY}" || ! "${FIREBASE_APP_ID}" ]]; then - : "${SERVICE_PLIST:="$(find "${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}" -name GoogleService-Info.plist | head -n1)"}" - : "${SERVICE_PLIST:?"GoogleService-Info.plist could not be located"}" - : "${FIREBASE_API_KEY:="$(property API_KEY "${SERVICE_PLIST}")"}" - : "${FIREBASE_APP_ID:="$(property GOOGLE_APP_ID "${SERVICE_PLIST}")"}" -fi - -if ! [[ "${FIREBASE_API_KEY}" ]]; then - xcerror "Unable to get API_KEY from ${SERVICE_PLIST}." - xcnote "Specify FIREBASE_API_KEY in environment." - exit 2 -fi - -if ! [[ "${FIREBASE_APP_ID}" ]]; then - xcerror "Unable to get GOOGLE_APP_ID from ${SERVICE_PLIST}." - xcnote "Specify FIREBASE_APP_ID in environment." - exit 2 -fi - -# Load Info.plist values (Bundle ID & version) - -INFOPLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}" - -if [[ -f "${INFOPLIST}" ]]; then - : "${FCR_PROD_VERS:="$(property CFBundleShortVersionString "${INFOPLIST}")"}" - : "${FCR_BUNDLE_ID:="$(property CFBundleIdentifier "${INFOPLIST}")"}" -fi - -if ! [[ "${FCR_PROD_VERS}" ]]; then - xcerror "Unable to get CFBundleShortVersionString from Info.plist." - xcnote "Specify FCR_PROD_VERS in environment." - exit 2 -fi - -if ! [[ "${FCR_BUNDLE_ID}" ]]; then - xcerror "Unable to get CFBundleIdentifier from Info.plist." - xcnote "Specify FCR_BUNDLE_ID in environment." - exit 2 -fi - -# Support legacy account file cache before giving up - -if [[ ! -f "${SERVICE_ACCOUNT_FILE}" ]]; then - xcwarning "Unable to find service account JSON file: ${SERVICE_ACCOUNT_FILE}" - "Please ensure you've followed the steps at:" - "https://firebase.google.com/docs/crash/ios#upload_symbol_files" - - xcdebug "Trying to extract JSON file from cache." - - CACHE_PLIST="${HOME}/Library/Preferences/com.google.SymbolUpload.plist" - - if [[ -f "${CACHE_PLIST}" ]]; then - fcr_mktemp SERVICE_ACCOUNT_FILE - /usr/bin/plutil -extract "app_${FIREBASE_APP_ID//:/_}" \ - json -o "${SERVICE_ACCOUNT_FILE}" "${CACHE_PLIST}" >/dev/null 2>&1 - if [[ ! -s "${SERVICE_ACCOUNT_FILE}" ]]; then - xcwarning "${FIREBASE_APP_ID} not found in cache." - /bin/rm -f "${SERVICE_ACCOUNT_FILE}" - else - xcnote "${FIREBASE_APP_ID} found in cache. Consider using extract-keys.pl to reduce reliance on cache." - fi - else - xcnote "No cache file found." - fi -fi - -if [[ ! -f "${SERVICE_ACCOUNT_FILE}" ]]; then - xcerror "All attempts to find the service account JSON file have failed." - xcnote "You must supply it on the command line." - echo >&2 -n "$0:1: note: "; usage - exit 2 -fi - -# Dump collected information if requested - -if ((VERBOSE >= 2)); then - xcnote "FIREBASE_API_KEY = ${FIREBASE_API_KEY}" - xcnote "FIREBASE_APP_ID = ${FIREBASE_APP_ID}" - xcnote "DSYM_BUNDLE = ${DSYM_BUNDLE:-(unset, will use symbols in executable)}" - xcnote "EXECUTABLE = ${EXECUTABLE}" - xcnote "INFOPLIST = ${INFOPLIST}" - xcnote "FCR_PROD_VERS = ${FCR_PROD_VERS}" - xcnote "FCR_BUNDLE_ID = ${FCR_BUNDLE_ID}" -fi - -# Create and upload symbol files for each architecture -if [[ -x "${SWIFT_DEMANGLE:=$(xcrun --find swift-demangle 2>/dev/null)}" ]]; then - SWIFT_DEMANGLE_COMMAND="${SWIFT_DEMANGLE} -simplified" -else - SWIFT_DEMANGLE_COMMAND=/bin/cat -fi - -for ARCH in ${ARCHS?:}; do - SYMBOL_FILE="SYMBOL_FILE_${ARCH}" - fcr_mktemp "${SYMBOL_FILE}" SCRATCH - - # Just because there is a dSYM bundle at that path does not mean - # it is the RIGHT dSYM bundle... - - if [[ -d "${DSYM_BUNDLE}" ]]; then - DSYM_UUID="$(dwarfdump --arch "${ARCH}" --uuid "${DSYM_BUNDLE}" | awk '{print $2}')" - EXE_UUID="$(dwarfdump --arch "${ARCH}" --uuid "${EXECUTABLE}" | awk '{print $2}')" - if ((VERBOSE > 1)); then - xcnote "dSYM bundle UUID: ${DSYM_UUID}" - xcnote "Executable UUID: ${EXE_UUID}" - fi - if [[ "${DSYM_UUID}" != "${EXE_UUID}" ]]; then - xcdebug "Current dSYM bundle is not valid." - unset DSYM_BUNDLE - fi - fi - - if [[ ! -d "${DSYM_BUNDLE}" ]]; then - xcdebug "Extracting dSYM from executable." - fcr_mktempdir TMP_DSYM - DSYM_BUNDLE="${TMP_DSYM}/${EXECUTABLE##*/}.dSYM" - xcrun dsymutil -o "${DSYM_BUNDLE}" "${EXECUTABLE}" - STATUS=$? - if ((STATUS)); then - xcerror "Command dsymutil failed with exit code ${STATUS}." - exit ${STATUS} - fi - fi - - "${FCR_DUMP_SYMS}" -a "${ARCH}" -g "${DSYM_BUNDLE}" "${EXECUTABLE}" >"${SCRATCH}" 2> >(sed -e 's/^/warning: dump_syms: /' | grep -v 'failed to demangle' >&2) - - STATUS=$? - if ((STATUS)); then - xcerror "Command dump_syms failed with exit code ${STATUS}." - exit ${STATUS} - fi - - ${SWIFT_DEMANGLE_COMMAND} <"${SCRATCH}" >|"${!SYMBOL_FILE}" || exit 1 - - if ((VERBOSE >= 2)); then - xcnote "${EXECUTABLE##*/} (architecture ${ARCH}) symbol dump follows (first 20 lines):" - head >&2 -n20 "${!SYMBOL_FILE}" - elif ((VERBOSE >= 1)); then - xcnote "${EXECUTABLE##*/} (architecture ${ARCH}) symbol dump follows (first line only):" - head >&2 -n1 "${!SYMBOL_FILE}" - fi - - fcr_upload_files "${!SYMBOL_FILE}" || exit 1 -done diff --git a/ZipBuilder/Template/Crash/upload-sym-util.bash b/ZipBuilder/Template/Crash/upload-sym-util.bash deleted file mode 100644 index e98b0b1b189..00000000000 --- a/ZipBuilder/Template/Crash/upload-sym-util.bash +++ /dev/null @@ -1,396 +0,0 @@ -# Copyright 2019 Google -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Output a clickable message. This will not count as a warning or -# error. - -xcnote () { - echo >&2 "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: note: $*" -} - -# Output a clickable message prefixed with a warning symbol (U+26A0) -# and highlighted yellow. This will increase the overall warning -# count. A non-zero value for the variable ERRORS_ONLY will force -# warnings to be treated as errors. - -if ((ERRORS_ONLY)); then - xcwarning () { - echo >&2 "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: error: $*" - } -else - xcwarning () { - echo >&2 "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: warning: $*" - } -fi - -# Output a clickable message prefixed with a halt symbol (U+1F6D1) and -# highlighted red. This will increase the overall error count. Xcode -# will flag the build as failed if the error count is non-zero at the -# end of the build, even if this script returns a successful exit -# code. Set WARNINGS_ONLY to non-zero to prevent this. - -if ((WARNINGS_ONLY)); then - xcerror () { - echo >&2 "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: warning: $*" - } -else - xcerror () { - echo >&2 "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: error: $*" - } -fi - -xcdebug () { - if ((VERBOSE)); then - echo >&2 "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: note: $*" - fi -} - -# Locate the script directory. - -script_dir () { - local SCRIPT="$0" SCRIPT_DIR="$(dirname "$0")" - - while SCRIPT="$(readlink "${SCRIPT}")"; do - [[ "${SCRIPT}" != /* ]] && SCRIPT="${SCRIPT_DIR}/${SCRIPT}" - SCRIPT_DIR="$(dirname "${SCRIPT}")" - done - - ( cd "${SCRIPT_DIR}"; pwd -P ) -} - -# Timestamp needed for various operations. Does not need to be exact, -# but does need to be consistent across web service calls. - -readonly NOW="$(/bin/date +%s)" - -# All files created by fcr_mktemp will be listed in FCR_TEMPORARY_FILES. -# Delete these when the enclosing script exits. (You may manually -# add files to this array as well to have them cleaned up on exit.) - -typeset -a FCR_TEMPORARY_FILES -trap 'STATUS=$?; rm -rf "${FCR_TEMPORARY_FILES[@]}"; exit ${STATUS}' 0 1 2 15 - -# Create a temporary file and add it to the list of files to delete when the -# script finishes. -# -# usage: fcr_mktemp VARNAME... - -fcr_mktemp () { - for VAR; do - eval "${VAR}=\$(mktemp -t com.google.FIRCrash) || return 1" - FCR_TEMPORARY_FILES+=("${!VAR}") - done -} - -# Create a temporary directory and add it to the list of files to -# delete when the script finishes. -# -# usage: fcr_mktempdir VARNAME... - -fcr_mktempdir () { - for VAR; do - eval "${VAR}=\$(mktemp -d -t com.google.FIRCrash) || return 1" - FCR_TEMPORARY_FILES+=("${!VAR}") - done -} - -# The keys we care about in the JSON objects. There are others that -# we do not use. Note that 'expires_at' and 'app_id' are not part of -# the original payload, but are computed from the environment used to -# make the call. - -FCR_SVC_KEYS=(client_email private_key private_key_id token_uri type) -FCR_TOK_KEYS=(access_token expires_at token_type app_id) - -# Extract a value from the property list. -# -# usage: property *name* *file* - -property () { - [[ -f "$2" ]] || echo '{}' >|"$2" # keeps PlistBuddy quiet - /usr/libexec/PlistBuddy "$2" -c "Print :$1" 2>/dev/null -} - -# Retrieve the property from the service account property list. -# -# usage: svc_property *name* - -svc_property () { - property "$1" "${SVC_PLIST}" -} - -# Does the same as svc_property above but for the token cache -# property list. -# -# usage: tok_property *name* - -tok_property () { - property "$1" "${TOK_PLIST}" -} - -# Verify that the service account property list has values for the -# required keys. Does not check the values themselves. - -fcr_verify_svc_plist () { - for key in "${FCR_SVC_KEYS[@]}"; do - if ! svc_property "${key}" >/dev/null; then - xcdebug "${key} not found in ${SVC_PLIST}. Service account invalid." - return 1 - fi - done -} - -# Verify that the token cache property list has values for the -# required keys. If the token_type is incorrect, the expiration date -# has been passed, or the application id does not match, return -# failure. - -fcr_verify_tok_plist () { - for key in "${FCR_TOK_KEYS[@]}"; do - if ! tok_property "${key}" >/dev/null; then - xcdebug "${key} not found in ${TOK_PLIST}. Token invalid." - return 1 - fi - done - - if [[ "$(tok_property token_type)" != "Bearer" ]]; then - xcwarning "Invalid token type '$(tok_property token_type)'." - return 1 - fi - - if (($(tok_property expires_at) <= NOW)); then - xcdebug "Token well-formed but expired at $(date -jf %s "$(tok_property expires_at)")." - echo '{}' >|"${TOK_PLIST}" - return 1 - fi - - if [[ "$(tok_property app_id)" != "${FIREBASE_APP_ID}" ]]; then - xcdebug "Cached token is for a different application." - echo '{}' >|"${TOK_PLIST}" - return 1 - fi -} - -# Convert a JSON certificate file to a PList certificate file. -# -# usage: fcr_load_certificate VARNAME - -fcr_load_certificate () { - : "${SERVICE_ACCOUNT_FILE:?must be the path to the service account JSON file.}" - fcr_mktemp "$1" - - if ! /usr/bin/plutil -convert binary1 "${SERVICE_ACCOUNT_FILE}" -o "${!1}"; then - xcerror "Unable to read service account file ${SERVICE_ACCOUNT_FILE}." - return 2 - fi -} - -# BASE64URL uses a sligtly different character set than BASE64, and -# uses no padding characters. - -function base64url () { - /usr/bin/base64 | sed -e 's/=//g; s/+/-/g; s/\//_/g' -} - -# Assemble the JSON Web Token (RFC 1795) -# -# usage: fcr_create_jwt *client-email* *token-uri* - -fcr_create_jwt () { - local JWT_HEADER="$(base64url <<<'{"alg":"RS256","typ":"JWT"}')" - local JWT_CLAIM="$(base64url <<<'{'"\"iss\":\"${1:?}\",\"aud\":\"${2:?}\",\"exp\":\"$((NOW + 3600))\",\"iat\":\"${NOW}\",\"scope\":\"https://www.googleapis.com/auth/mobilecrashreporting\""'}')" - local JWT_BODY="${JWT_HEADER}.${JWT_CLAIM}" - local JWT_SIG="$(echo -n "${JWT_BODY}" | openssl dgst -sha256 -sign <(svc_property private_key) -binary | base64url)" - - echo "${JWT_BODY}.${JWT_SIG}" -} - -# Set the BEARER_TOKEN variable for authentication. -# -# usage: fcr_authenticate - -fcr_authenticate () { - : "${FIREBASE_APP_ID:?required to select authentication credentials}" - - local SVC_PLIST - - fcr_load_certificate SVC_PLIST || return 2 - - local TOK_PLIST="${HOME}/Library/Preferences/com.google.SymbolUploadToken.plist" - - if ((VERBOSE > 2)); then - CURLOPT='--trace-ascii /dev/fd/2' - elif ((VERBOSE > 1)); then - CURLOPT='--verbose' - else - CURLOPT='' - fi - - # If the token will expire in the next sixty seconds (or already - # has), reload it. - if ! fcr_verify_tok_plist; then - xcdebug "Token cannot be used. Requesting OAuth2 token using installed credentials." - - if ! fcr_verify_svc_plist; then - xcerror "Incorrect/incomplete service account file." - return 2 - else - xcdebug "Certificate information appears valid." - fi - - TOKEN_URI="$(svc_property token_uri)" - CLIENT_EMAIL="$(svc_property client_email)" - - # Assemble the JSON Web Token (RFC 1795) - local JWT="$(fcr_create_jwt "${CLIENT_EMAIL}" "${TOKEN_URI}")" - - fcr_mktemp TOKEN_JSON - - HTTP_STATUS="$(curl ${CURLOPT} -o "${TOKEN_JSON}" -s -d grant_type='urn:ietf:params:oauth:grant-type:jwt-bearer' -d assertion="${JWT}" -w '%{http_code}' "${TOKEN_URI}")" - - if [[ "${HTTP_STATUS}" == 403 ]]; then - xcerror "Invalid certificate. Unable to retrieve OAuth2 token." - return 2 - elif [[ "${HTTP_STATUS}" != 200 ]]; then - cat >&2 "${TOKEN_JSON}" - return 2 - fi - - # Store the token in the preferences directory for future use. - /usr/bin/plutil -convert binary1 "${TOKEN_JSON}" -o "${TOK_PLIST}" - - EXPIRES_IN="$(tok_property expires_in)" - EXPIRES_AT="$((EXPIRES_IN + NOW))" - - /usr/libexec/PlistBuddy \ - -c "Add :app_id string \"${FIREBASE_APP_ID}\"" \ - -c "Add :expires_at integer ${EXPIRES_AT}" \ - -c "Add :expiration_date date $(TZ=GMT date -jf %s ${EXPIRES_AT})" \ - "${TOK_PLIST}" - - if ! fcr_verify_tok_plist; then - ((VERBOSE)) && /usr/libexec/PlistBuddy -c 'Print' "${TOK_PLIST}" - - echo '{}' >|"${TOK_PLIST}" - xcwarning "Token returned is not valid." - xcnote "If this error persists, download a fresh certificate." - - return 2 - fi - else - xcdebug "Token still valid." - EXPIRES_AT="$(tok_property expires_at)" - fi - - xcdebug "Token will expire on $(date -jf %s "${EXPIRES_AT}")." - xcdebug "Using service account with key $(svc_property private_key_id)." - - BEARER_TOKEN="$(tok_property access_token)" - - if [[ ! "${BEARER_TOKEN}" ]]; then - if ((VERBOSE)); then - xcwarning "Current malformed token cache:" - tok_property | while read; do xcnote "${REPLY}"; done - fi - xcerror "Unable to retrieve authentication token from server." - return 2 - fi - - return 0 -} - -# Upload the files to the server. -# -# Arguments: Names of files to upload. - -fcr_upload_files() { - fcr_authenticate || return $? - - : "${FCR_PROD_VERS:?}" - : "${FCR_BUNDLE_ID:?}" - : "${FIREBASE_APP_ID:?}" - : "${FIREBASE_API_KEY:?}" - : "${FCR_BASE_URL:=https://mobilecrashreporting.googleapis.com}" - - fcr_mktemp FILE_UPLOAD_LOCATION_PLIST META_UPLOAD_RESULT_PLIST - - if ((VERBOSE > 2)); then - CURLOPT='--trace-ascii /dev/fd/2' - elif ((VERBOSE > 1)); then - CURLOPT='--verbose' - else - CURLOPT='' - fi - - for FILE; do - xcdebug "Get signed URL for uploading." - - URL="${FCR_BASE_URL}/v1/apps/${FIREBASE_APP_ID}" - - HTTP_STATUS="$(curl ${CURLOPT} -o "${FILE_UPLOAD_LOCATION_PLIST}" -sL -H "X-Ios-Bundle-Identifier: ${FCR_BUNDLE_ID}" -H "Authorization: Bearer ${BEARER_TOKEN}" -X POST -d '' -w '%{http_code}' "${URL}/symbolFileUploadLocation?key=${FIREBASE_API_KEY}")" - STATUS=$? - - if [[ "${STATUS}" == 22 && "${HTTP_STATUS}" == 403 ]]; then - xcerror "Unable to access resource. Token invalid." - xcnote "Please verify the service account file." - return 2 - elif [[ "${STATUS}" != 0 ]]; then - xcerror "curl exited with non-zero status ${STATUS}." - ((STATUS == 22)) && xcerror "HTTP response code is ${HTTP_STATUS}." - return 2 - fi - - /usr/bin/plutil -convert binary1 "${FILE_UPLOAD_LOCATION_PLIST}" || return 1 - - UPLOAD_KEY="$(property uploadKey "${FILE_UPLOAD_LOCATION_PLIST}")" - UPLOAD_URL="$(property uploadUrl "${FILE_UPLOAD_LOCATION_PLIST}")" - ERRMSG="$(property error:message "${FILE_UPLOAD_LOCATION_PLIST}")" - - if [[ "${ERRMSG}" ]]; then - if ((VERBOSE)); then - xcnote "Server response:" - /usr/bin/plutil -p "${FILE_UPLOAD_LOCATION_PLIST}" >&2 - fi - xcerror "symbolFileUploadLocation: ${ERRMSG}" - xcnote "symbolFileUploadLocation: Failed to get upload location." - return 1 - fi - - xcdebug "Upload symbol file." - - HTTP_STATUS=$(curl ${CURLOPT} -sfL -H 'Content-Type: text/plain' -H "Authorization: Bearer ${BEARER_TOKEN}" -w '%{http_code}' -T "${FILE}" "${UPLOAD_URL}") - STATUS=$? - - if ((STATUS == 22)); then # exit code 22 is a non-successful HTTP response - xcerror "upload: Unable to upload symbol file (HTTP Status ${HTTP_STATUS})." - return 1 - elif ((STATUS != 0)); then - xcerror "upload: Unable to upload symbol file (reason unknown)." - return 1 - fi - - xcdebug "Upload metadata information." - - curl ${CURLOPT} -sL -H 'Content-Type: application/json' -H "X-Ios-Bundle-Identifier: ${FCR_BUNDLE_ID}" -H "Authorization: Bearer ${BEARER_TOKEN}" -X POST -d '{"upload_key":"'"${UPLOAD_KEY}"'","symbol_file_mapping":{"symbol_type":2,"app_version":"'"${FCR_PROD_VERS}"'"}}' "${URL}/symbolFileMappings:upsert?key=${FIREBASE_API_KEY}" >|"${META_UPLOAD_RESULT_PLIST}" || return 1 - /usr/bin/plutil -convert binary1 "${META_UPLOAD_RESULT_PLIST}" || return 1 - - ERRMSG="$(property error:message "${META_UPLOAD_RESULT_PLIST}")" - - if [[ "${ERRMSG}" ]]; then - xcerror "symbolFileMappings:upsert: ${ERRMSG}" - xcnote "symbolFileMappings:upsert: The metadata for the symbol file failed to update." - return 1 - fi - done -} diff --git a/ZipBuilder/Template/Firebase.h b/ZipBuilder/Template/Firebase.h index e7b4d649be6..9420658a2f2 100644 --- a/ZipBuilder/Template/Firebase.h +++ b/ZipBuilder/Template/Firebase.h @@ -36,10 +36,6 @@ Firebase services work as intended." #import #endif -#if __has_include() -#import -#endif - #if __has_include() #import #endif @@ -64,10 +60,6 @@ Firebase services work as intended." #import #endif -#if __has_include() -#import -#endif - #if __has_include() #import #endif diff --git a/cmake/cc_rules.cmake b/cmake/cc_rules.cmake index bf376102a96..39b56c9fcd8 100644 --- a/cmake/cc_rules.cmake +++ b/cmake/cc_rules.cmake @@ -30,7 +30,7 @@ function(cc_library name) maybe_remove_objc_sources(sources ${ccl_SOURCES}) add_library(${name} ${sources}) - add_objc_flags(${name} ccl) + add_objc_flags(${name} ${ccl_SOURCES}) target_include_directories( ${name} PUBLIC @@ -107,7 +107,7 @@ function(cc_binary name) maybe_remove_objc_sources(sources ${ccb_SOURCES}) add_executable(${name} ${sources}) - add_objc_flags(${name} ccb) + add_objc_flags(${name} ${ccb_SOURCES}) add_test(${name} ${name}) target_include_directories(${name} PUBLIC ${FIREBASE_SOURCE_DIR}) @@ -137,7 +137,7 @@ function(cc_test name) maybe_remove_objc_sources(sources ${cct_SOURCES}) add_executable(${name} ${sources}) - add_objc_flags(${name} cct) + add_objc_flags(${name} ${cct_SOURCES}) add_test(${name} ${name}) target_include_directories(${name} PUBLIC ${FIREBASE_SOURCE_DIR}) diff --git a/scripts/build.sh b/scripts/build.sh index 038cd7ecbe1..2686c57ca7f 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -304,7 +304,22 @@ case "$product-$method-$platform" in -scheme "Firestore_IntegrationTests_$platform" \ "${xcb_flags[@]}" \ build + ;; + Firestore-xcodebuild-macOS | Firestore-xcodebuild-tvOS) + # TODO(wilhuff): Combine with above once all targets exist + RunXcodebuild \ + -workspace 'Firestore/Example/Firestore.xcworkspace' \ + -scheme "Firestore_Tests_$platform" \ + "${xcb_flags[@]}" \ + build \ + test + + RunXcodebuild \ + -workspace 'Firestore/Example/Firestore.xcworkspace' \ + -scheme "Firestore_IntegrationTests_$platform" \ + "${xcb_flags[@]}" \ + build ;; Firestore-cmake-macOS) diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 00000000000..7e1243cde86 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,221 @@ +#!/bin/bash + +# Copyright 2019 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Checks that the current state of the tree is sane and optionally auto-fixes +# errors automatically. Meant for interactive use. + +function usage() { + cat <] + +Runs auto-formatting scripts, source-tree checks, and linters on any files that +have changed since master. + +By default, any changes are left as uncommited changes in the working tree. You +can review them with git diff. Pass --commit to automatically commit any changes. + +Pass an alternate revision to use as the basis for checking changes. + +OPTIONS: + + --allow-dirty + By default, check.sh requires a clean working tree to keep any generated + changes separate from logical changes. + + --commit + Commit any auto-generated changes with a message indicating which tool made + the changes. + + --amend + Commit any auto-generated changes by amending the HEAD commit. + + --fixup + Commit any auto-generated changes with a fixup! message for the HEAD + commit. The next rebase will squash these fixup commits. + + --test-only + Run all checks without making any changes to local files. + + + Specifies a starting revision other than the default of master. + + +EXAMPLES: + + check.sh + Runs automated checks and formatters on all changed files since master. + Check for changes with git diff. + + check.sh --commit + Runs automated checks and formatters on all changed files since master and + commits the results. + + check.sh --amend HEAD + Runs automated checks and formatters on all changed files since the last + commit and amends the last commit with the difference. + + check.sh --allow-dirty HEAD + Runs automated checks and formatters on all changed files since the last + commit and intermingles the changes with any pending changes. Useful for + interactive use from an editor. + +EOF +} + +set -euo pipefail +unset CDPATH + +# Change to the top-directory of the working tree +top_dir=$(git rev-parse --show-toplevel) +cd "${top_dir}" + +ALLOW_DIRTY=false +COMMIT_METHOD="none" +START_REVISION="master" +TEST_ONLY=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --) + # Do nothing: explicitly allow this, but ignore it + ;; + + -h | --help) + usage + exit 1 + ;; + + --allow-dirty) + ALLOW_DIRTY=true + ;; + + --amend) + COMMIT_METHOD=amend + ;; + + --fixup) + COMMIT_METHOD=fixup + ;; + + --commit) + COMMIT_METHOD=message + ;; + + --test-only) + # In test-only mode, no changes are made, so there's no reason to + # require a clean source tree. + ALLOW_DIRTY=true + TEST_ONLY=true + ;; + + *) + if git rev-parse "$1" >& /dev/null; then + START_REVISION="$1" + break + fi + ;; + esac + shift +done + +if [[ "${TEST_ONLY}" == true && "${COMMIT_METHOD}" != "none" ]]; then + echo "--test-only cannot be combined with --amend, --fixup, or --commit" + exit 1 +fi + +if [[ "${ALLOW_DIRTY}" == true && "${COMMIT_METHOD}" == "message" ]]; then + echo "--allow-dirty and --commit are mutually exclusive" + exit 1 +fi + +if ! git diff-index --quiet HEAD --; then + if [[ "${ALLOW_DIRTY}" != true ]]; then + echo "You have local changes that could be overwritten by this script." + echo "Please commit your changes first or pass --allow-dirty." + exit 2 + fi +fi + +# Record actual start, but only if the revision is specified as a single +# commit. Ranges specified with .. or ... are left alone. +if [[ "${START_REVISION}" == *..* ]]; then + START_SHA="${START_REVISION}" +else + START_SHA=$(git rev-parse "${START_REVISION}") +fi + +# If committing --fixup, avoid messages with fixup! fixup! that might come from +# multiple fixup commits. +HEAD_SHA=$(git rev-parse HEAD) + +function maybe_commit() { + local message="$1" + + if [[ "${COMMIT_METHOD}" == "none" ]]; then + return + fi + + echo "${message}" + case "${COMMIT_METHOD}" in + amend) + git commit -a --amend -C "${HEAD_SHA}" + ;; + + fixup) + git commit -a --fixup="${HEAD_SHA}" + ;; + + message) + git commit -a -m "${message}" + ;; + + *) + echo "Unknown commit method ${COMMIT_METHOD}" 1>&2 + exit 2 + ;; + esac +} + +style_cmd=("${top_dir}/scripts/style.sh") +if [[ "${TEST_ONLY}" == true ]]; then + style_cmd+=(test-only) +fi +style_cmd+=("${START_SHA}") + +# Restyle and commit any changes +"${style_cmd[@]}" +if ! git diff --quiet; then + maybe_commit "style.sh generated changes" +fi + +# If there are changes to the Firestore project, ensure they're ordered +# correctly to minimize conflicts. +if ! git diff --quiet "${START_SHA}" -- Firestore; then + "${top_dir}/scripts/sync_project.rb" + if ! git diff --quiet; then + maybe_commit "sync_project.rb generated changes" + fi +fi + +# Check lint errors. +"${top_dir}/scripts/check_whitespace.sh" +"${top_dir}/scripts/check_filename_spaces.sh" +"${top_dir}/scripts/check_copyright.sh" +"${top_dir}/scripts/check_no_module_imports.sh" +"${top_dir}/scripts/check_test_inclusion.py" + +# Google C++ style +"${top_dir}/scripts/lint.sh" "${START_SHA}" diff --git a/ZipBuilder/Template/Crash/upload-sym.sh b/scripts/check_filename_spaces.sh similarity index 52% rename from ZipBuilder/Template/Crash/upload-sym.sh rename to scripts/check_filename_spaces.sh index 5fc7d1a2b3e..5e48c489d92 100755 --- a/ZipBuilder/Template/Crash/upload-sym.sh +++ b/scripts/check_filename_spaces.sh @@ -14,5 +14,26 @@ # See the License for the specific language governing permissions and # limitations under the License. -echo "$0:0: error: $0 has been removed. Please use upload-sym instead." -exit 1 +# Fail on spaces in file names, excluding the patterns listed below. + +# A sed program that removes filename patterns that are allowed to have spaces +# in them. +function remove_valid_names() { + sed ' + # Xcode-generated asset files + /Assets.xcassets/ d + + # Files without spaces + /^[^ ]*$/ d + ' +} + +count=$(git ls-files | remove_valid_names | wc -l | xargs) + +if [[ ${count} != 0 ]]; then + echo 'ERROR: Spaces in filenames are not permitted in this repo. Please fix.' + echo '' + + git ls-files | remove_valid_names + exit 1 +fi diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 51fbe246ae0..26d8f7f983c 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -211,6 +211,7 @@ 'readability/namespace', 'readability/nolint', 'readability/nul', + 'readability/objc', 'readability/strings', 'readability/todo', 'readability/utf8', @@ -553,6 +554,9 @@ # This is set by --headers flag. _hpp_headers = set(['h']) +# Source filename extensions +_cpp_extensions = set(['cc', 'mm']) + # {str, bool}: a map from error categories to booleans which indicate if the # category should be suppressed for every line. _global_error_suppressions = {} @@ -569,6 +573,15 @@ def ProcessHppHeadersOption(val): def IsHeaderExtension(file_extension): return file_extension in _hpp_headers +def IsSourceExtension(file_extension): + return file_extension in _cpp_extensions + +def IsSourceFilename(filename): + global _cpp_extensions + ext = os.path.splitext(filename)[-1].lower() + ext = ext[1:] # leading dot + return IsSourceExtension(ext) + def ParseNolintSuppressions(filename, raw_line, linenum, error): """Updates the global list of line error-suppressions. @@ -4579,7 +4592,7 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): error(filename, linenum, 'build/include', 4, '"%s" already included at %s:%s' % (include, filename, duplicate_line)) - elif (include.endswith('.cc') and + elif (IsSourceFilename(include) and os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)): error(filename, linenum, 'build/include', 4, 'Do not include .cc files from other packages') @@ -4611,6 +4624,30 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): include_state.SetLastHeader(canonical_include) +_BANNED_OBJC_WORDS = (r'\bYES\b', r'\bNO\b', r'\bBOOL\b') +_BANNED_OBJC_REPLACEMENTS = { + 'YES': 'true', + 'NO': 'false', + 'BOOL': 'bool', +} + +_BANNED_OBJC_WORDS_RE = re.compile('(' + '|'.join(_BANNED_OBJC_WORDS) + ')') + + +def CheckObjcConversion(filename, lines, error): + if 'Firestore/core/' not in filename: + return + + for linenum, line in enumerate(lines): + match = _BANNED_OBJC_WORDS_RE.search(line) + if match: + word = match.group(1) + replacement = _BANNED_OBJC_REPLACEMENTS[word] + error(filename, linenum, 'readability/objc', 4, + 'Migrate %s to %s' % + (word, replacement)) + + def _GetTextInside(text, start_pattern): r"""Retrieves all the text between matching open and close parentheses. @@ -5390,6 +5427,7 @@ def ExpectingFunctionArgs(clean_lines, linenum): ('', ('map', 'multimap',)), ('', ('allocator', 'make_shared', 'make_unique', 'shared_ptr', 'unique_ptr', 'weak_ptr')), + ('', ('ostream',)), ('', ('queue', 'priority_queue',)), ('', ('set', 'multiset',)), ('', ('stack',)), @@ -5415,6 +5453,7 @@ def ExpectingFunctionArgs(clean_lines, linenum): ) _RE_PATTERN_STRING = re.compile(r'\bstring\b') +_RE_PATTERN_OSTREAM = re.compile(r'\bostream\b') _re_pattern_headers_maybe_templates = [] for _header, _templates in _HEADERS_MAYBE_TEMPLATES: @@ -5536,8 +5575,17 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io: The IO factory to use to read the header file. Provided for unittest injection. """ - required = {} # A map of header name to linenumber and the template entity. - # Example of required: { '': (1219, 'less<>') } + # A map of entity to a tuple of line number and tuple of headers. + # Example: { 'less<>': (1219, ('',)) } + # Example: { 'ostream': (1234, ('', '', '')) } + required = {} + + def Require(entity, linenum, *headers): + """Adds an entity at the given line, along with a list of possible headers + in which to find it. The first header is treated as the preferred header. + """ + required[entity] = (linenum, headers) + for linenum in xrange(clean_lines.NumLines()): line = clean_lines.elided[linenum] @@ -5551,11 +5599,19 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, # (We check only the first match per line; good enough.) prefix = line[:matched.start()] if prefix.endswith('std::') or not prefix.endswith('::'): - required[''] = (linenum, 'string') + Require('string', linenum, '') + + # Ostream is special too -- also non-templatized + matched = _RE_PATTERN_OSTREAM.search(line) + if matched: + if IsSourceFilename(filename): + Require('ostream', linenum, '', '') + else: + Require('ostream', linenum, '', '', '') for pattern, template, header in _re_pattern_headers_maybe_templates: if pattern.search(line): - required[header] = (linenum, template) + Require(template, linenum, header) # The following function is just a speed up, no semantics are changed. if not '<' in line: # Reduces the cpu time usage by skipping lines. @@ -5568,7 +5624,7 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, # (We check only the first match per line; good enough.) prefix = line[:matched.start()] if prefix.endswith('std::') or not prefix.endswith('::'): - required[header] = (linenum, template) + Require(template, linenum, header) # The policy is that if you #include something in foo.h you don't need to # include it again in foo.cc. Here, we will look at possible includes. @@ -5605,16 +5661,37 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, # didn't include it in the .h file. # TODO(unknown): Do a better job of finding .h files so we are confident that # not having the .h file means there isn't one. - if filename.endswith('.cc') and not header_found: + if IsSourceFilename(filename) and not header_found: return + # Keep track of which headers have been reported already + reported = set() + # All the lines have been processed, report the errors found. - for required_header_unstripped in required: - template = required[required_header_unstripped][1] - if required_header_unstripped.strip('<>"') not in include_dict: - error(filename, required[required_header_unstripped][0], + for template in required: + line_and_headers = required[template] + headers = line_and_headers[1] + found = False + for required_header_unstripped in headers: + if required_header_unstripped in reported: + found = True + break + + if required_header_unstripped.strip('<>"') in include_dict: + found = True + break + + if not found: + preferred_header = headers[0] + reported.add(preferred_header) + if len(headers) < 2: + alternatives = '' + else: + alternatives = ' (or ' + ', '.join(headers[1:]) + ')' + error(filename, line_and_headers[0], 'build/include_what_you_use', 4, - 'Add #include ' + required_header_unstripped + ' for ' + template) + 'Add #include ' + preferred_header + ' for ' + template + + alternatives) _RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<') @@ -5950,6 +6027,8 @@ def ProcessFileData(filename, file_extension, lines, error, ResetNolintSuppressions() + CheckObjcConversion(filename, lines, error) + CheckForCopyright(filename, lines, error) ProcessGlobalSuppresions(lines) RemoveMultiLineComments(filename, lines, error) diff --git a/scripts/if_changed.sh b/scripts/if_changed.sh index 27261a4ef9b..0777f6693e7 100755 --- a/scripts/if_changed.sh +++ b/scripts/if_changed.sh @@ -53,7 +53,8 @@ else 'FirebaseMessaging.podspec|FirebaseStorage.podspec|'\ 'FirebaseStorage.podspec|Firebase/InAppMessagingDisplay|InAppMessagingDisplay|'\ 'InAppMessaging|Firebase/InAppMessaging|'\ -'FirebaseInAppMessaging.podspec|FirebaseInAppMessagingDisplay.podspec)' +'FirebaseInAppMessaging.podspec|FirebaseInAppMessagingDisplay.podspec|'\ +'Firebase/InstanceID|FirebaseInstanceID.podspec)' ;; Firebase-*) @@ -62,7 +63,7 @@ else 'FirebaseAnalyticsIntop.podspec|FirebaseAuth.podspec|FirebaseAuthInterop.podspec|'\ 'FirebaseCore.podspec|FirebaseDatabase.podspec|FirebaseDynamicLinks.podspec|'\ 'FirebaseMessaging.podspec|FirebaseStorage.podspec|'\ -'FirebaseStorage.podspec)' +'FirebaseStorage.podspec|Firebase/InstanceID|FirebaseInstanceID.podspec)' ;; Functions-*) diff --git a/scripts/if_cron.sh b/scripts/if_cron.sh deleted file mode 100755 index c13a374a238..00000000000 --- a/scripts/if_cron.sh +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2018 Google -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Within Travis, check if running in a cron job. -# -# Examines the following Travis-supplied environment variables: -# - TRAVIS_EVENT_TYPE - to check if this is a cron job -# - -if [[ "$TRAVIS_EVENT_TYPE" == "cron" ]]; then - "$@" -else - echo "skipped $*" -fi diff --git a/scripts/pod_lib_lint.sh b/scripts/pod_lib_lint.sh index f6d6f2e67b2..a9432c323a4 100755 --- a/scripts/pod_lib_lint.sh +++ b/scripts/pod_lib_lint.sh @@ -18,6 +18,8 @@ # # Runs pod lib lint for the given podspec +set -euo pipefail + if [[ $# -lt 1 ]]; then cat 1>&2 <