diff --git a/admob/integration_test/AndroidManifest.xml b/admob/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..0996b3d0df --- /dev/null +++ b/admob/integration_test/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/admob/integration_test/CMakeLists.txt b/admob/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..fdc3fbcd58 --- /dev/null +++ b/admob/integration_test/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_admob firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/admob/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/admob/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/admob/integration_test/Images.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/admob/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/admob/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/admob/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/admob/integration_test/Info.plist b/admob/integration_test/Info.plist new file mode 100644 index 0000000000..a3e8956703 --- /dev/null +++ b/admob/integration_test/Info.plist @@ -0,0 +1,37 @@ + + + + + GADApplicationIdentifier + ca-app-pub-3940256099942544~1458002511 + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.ios.admob.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + CFBundleURLTypes + + + CFBundleURLSchemes + + firebase-game-loop + + + + + diff --git a/admob/integration_test/LaunchScreen.storyboard b/admob/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/admob/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/admob/integration_test/LibraryManifest.xml b/admob/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..e1d761e160 --- /dev/null +++ b/admob/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/admob/integration_test/Podfile b/admob/integration_test/Podfile new file mode 100644 index 0000000000..23b23b5411 --- /dev/null +++ b/admob/integration_test/Podfile @@ -0,0 +1,15 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase AdMob test application. + +target 'integration_test' do + pod 'Firebase/AdMob', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/admob/integration_test/build.gradle b/admob/integration_test/build.gradle new file mode 100644 index 0000000000..1b40229275 --- /dev/null +++ b/admob/integration_test/build.gradle @@ -0,0 +1,76 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.android.admob.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + admob +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/admob/integration_test/googletest.cmake b/admob/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/admob/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/admob/integration_test/gradle/wrapper/gradle-wrapper.jar b/admob/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/admob/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/admob/integration_test/gradle/wrapper/gradle-wrapper.properties b/admob/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/admob/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/admob/integration_test/gradlew b/admob/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/admob/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/admob/integration_test/gradlew.bat b/admob/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/admob/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/admob/integration_test/integration_test.xcodeproj/project.pbxproj b/admob/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..57e3561511 --- /dev/null +++ b/admob/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,409 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; + FF88C17A940D28F103913070 /* libPods-integration_test.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E4B91AFE2AFA744509A9C25 /* libPods-integration_test.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1878FBCB416E0920F536416B /* Pods-integration_test.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-integration_test.release.xcconfig"; path = "Target Support Files/Pods-integration_test/Pods-integration_test.release.xcconfig"; sourceTree = ""; }; + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 8E4B91AFE2AFA744509A9C25 /* libPods-integration_test.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-integration_test.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + BFA4C698DDB19F60D12FED3A /* Pods-integration_test.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-integration_test.debug.xcconfig"; path = "Target Support Files/Pods-integration_test/Pods-integration_test.debug.xcconfig"; sourceTree = ""; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + FF88C17A940D28F103913070 /* libPods-integration_test.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + 78E4FA177005E3E6FE395AE2 /* Pods */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + 8E4B91AFE2AFA744509A9C25 /* libPods-integration_test.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; + 78E4FA177005E3E6FE395AE2 /* Pods */ = { + isa = PBXGroup; + children = ( + BFA4C698DDB19F60D12FED3A /* Pods-integration_test.debug.xcconfig */, + 1878FBCB416E0920F536416B /* Pods-integration_test.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 7DBB351D9F7518B4E087635C /* [CP] Check Pods Manifest.lock */, + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 7DBB351D9F7518B4E087635C /* [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-integration_test-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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BFA4C698DDB19F60D12FED3A /* Pods-integration_test.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1878FBCB416E0920F536416B /* Pods-integration_test.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/admob/integration_test/proguard.pro b/admob/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/admob/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/admob/integration_test/res/layout/main.xml b/admob/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/admob/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/admob/integration_test/res/values/strings.xml b/admob/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..f38ec9de9d --- /dev/null +++ b/admob/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase AdMob Integration Test + diff --git a/admob/integration_test/settings.gradle b/admob/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/admob/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/admob/integration_test/src/integration_test.cc b/admob/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..b97fbba2ef --- /dev/null +++ b/admob/integration_test/src/integration_test.cc @@ -0,0 +1,552 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include + +#include "app_framework.h" // NOLINT +#include "firebase/admob.h" +#include "firebase/app.h" +#include "firebase/util.h" +#include "firebase_test_framework.h" // NOLINT + +#if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +// includes for phone-only tests. +#include +#include +#endif // defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +// The AdMob app IDs for the test app. +#if defined(__ANDROID__) +// If you change the AdMob app ID for your Android app, make sure to change it +// in AndroidManifest.xml as well. +const char* kAdMobAppID = "ca-app-pub-3940256099942544~3347511713"; +#else +// If you change the AdMob app ID for your iOS app, make sure to change the +// value for "GADApplicationIdentifier" in your Info.plist as well. +const char* kAdMobAppID = "ca-app-pub-3940256099942544~1458002511"; +#endif + +// These ad units IDs have been created specifically for testing, and will +// always return test ads. +#if defined(__ANDROID__) +const char* kBannerAdUnit = "ca-app-pub-3940256099942544/6300978111"; +const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/1033173712"; +const char* kRewardedVideoAdUnit = "ca-app-pub-3940256099942544/5224354917"; +#else +const char* kBannerAdUnit = "ca-app-pub-3940256099942544/2934735716"; +const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/4411468910"; +const char* kRewardedVideoAdUnit = "ca-app-pub-3940256099942544/1712485313"; +#endif + +using app_framework::LogDebug; +using app_framework::ProcessEvents; + +using firebase_test_framework::FirebaseTest; + +class FirebaseAdMobTest : public FirebaseTest { + public: + FirebaseAdMobTest(); + ~FirebaseAdMobTest() override; + + static void SetUpTestSuite(); + static void TearDownTestSuite(); + + void SetUp() override; + void TearDown() override; + + protected: + firebase::admob::AdRequest GetAdRequest(); + + static firebase::App* shared_app_; +}; + +firebase::App* FirebaseAdMobTest::shared_app_ = nullptr; + +void FirebaseAdMobTest::SetUpTestSuite() { + LogDebug("Initialize Firebase App."); + + FindFirebaseConfig(FIREBASE_CONFIG_STRING); + +#if defined(__ANDROID__) + shared_app_ = ::firebase::App::Create(app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + shared_app_ = ::firebase::App::Create(); +#endif // defined(__ANDROID__) + + LogDebug("Initializing AdMob."); + + ::firebase::ModuleInitializer initializer; + initializer.Initialize( + shared_app_, nullptr, [](::firebase::App* app, void* /* userdata */) { + LogDebug("Try to initialize AdMob"); + return ::firebase::admob::Initialize(*app, kAdMobAppID); + }); + + WaitForCompletion(initializer.InitializeLastResult(), "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized AdMob."); +} + +void FirebaseAdMobTest::TearDownTestSuite() { + LogDebug("Shutdown AdMob."); + firebase::admob::Terminate(); + LogDebug("Shutdown Firebase App."); + delete shared_app_; + shared_app_ = nullptr; +} + +FirebaseAdMobTest::FirebaseAdMobTest() {} + +FirebaseAdMobTest::~FirebaseAdMobTest() {} + +void FirebaseAdMobTest::SetUp() { FirebaseTest::SetUp(); } + +void FirebaseAdMobTest::TearDown() { FirebaseTest::TearDown(); } + +firebase::admob::AdRequest FirebaseAdMobTest::GetAdRequest() { + // Sample keywords to use in making the request. + static const char* kKeywords[] = {"AdMob", "C++", "Fun"}; + + // Sample birthday value to use in making the request. + static const int kBirthdayDay = 10; + static const int kBirthdayMonth = 11; + static const int kBirthdayYear = 1976; + + // Sample test device IDs to use in making the request. + static const char* kTestDeviceIDs[] = {"2077ef9a63d2b398840261c8221a0c9b", + "098fe087d987c9a878965454a65654d7"}; + + firebase::admob::AdRequest request; + // If the app is aware of the user's gender, it can be added to the targeting + // information. Otherwise, "unknown" should be used. + request.gender = firebase::admob::kGenderUnknown; + + // This value allows publishers to specify whether they would like the request + // to be treated as child-directed for purposes of the Children’s Online + // Privacy Protection Act (COPPA). + // See http://business.ftc.gov/privacy-and-security/childrens-privacy. + request.tagged_for_child_directed_treatment = + firebase::admob::kChildDirectedTreatmentStateTagged; + + // The user's birthday, if known. Note that months are indexed from one. + request.birthday_day = kBirthdayDay; + request.birthday_month = kBirthdayMonth; + request.birthday_year = kBirthdayYear; + + // Additional keywords to be used in targeting. + request.keyword_count = sizeof(kKeywords) / sizeof(kKeywords[0]); + request.keywords = kKeywords; + + // "Extra" key value pairs can be added to the request as well. Typically + // these are used when testing new features. + static const firebase::admob::KeyValuePair kRequestExtras[] = { + {"the_name_of_an_extra", "the_value_for_that_extra"}}; + request.extras_count = sizeof(kRequestExtras) / sizeof(kRequestExtras[0]); + request.extras = kRequestExtras; + + // This example uses ad units that are specially configured to return test ads + // for every request. When using your own ad unit IDs, however, it's important + // to register the device IDs associated with any devices that will be used to + // test the app. This ensures that regardless of the ad unit ID, those + // devices will always receive test ads in compliance with AdMob policy. + // + // Device IDs can be obtained by checking the logcat or the Xcode log while + // debugging. They appear as a long string of hex characters. + request.test_device_id_count = + sizeof(kTestDeviceIDs) / sizeof(kTestDeviceIDs[0]); + request.test_device_ids = kTestDeviceIDs; + return request; +} + +// Test cases below. + +TEST_F(FirebaseAdMobTest, TestGetAdRequest) { GetAdRequest(); } + +// A simple listener to help test changes to a BannerView. +class TestBannerViewListener : public firebase::admob::BannerView::Listener { + public: + void OnPresentationStateChanged( + firebase::admob::BannerView* banner_view, + firebase::admob::BannerView::PresentationState state) override { + presentation_states_.push_back(state); + } + void OnBoundingBoxChanged(firebase::admob::BannerView* banner_view, + firebase::admob::BoundingBox box) override { + bounding_box_changes_.push_back(box); + } + std::vector + presentation_states_; + std::vector bounding_box_changes_; +}; + +TEST_F(FirebaseAdMobTest, TestBannerView) { + // AdMob cannot be tested on Firebase Test Lab, so disable tests on FTL. + TEST_REQUIRES_USER_INTERACTION; + + static const int kBannerWidth = 320; + static const int kBannerHeight = 50; + + firebase::admob::AdSize banner_ad_size; + banner_ad_size.ad_size_type = firebase::admob::kAdSizeStandard; + banner_ad_size.width = kBannerWidth; + banner_ad_size.height = kBannerHeight; + + firebase::admob::BannerView* banner = new firebase::admob::BannerView(); + WaitForCompletion(banner->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + + // Set the listener. + TestBannerViewListener banner_listener; + banner->SetListener(&banner_listener); + + // Load the banner ad. + firebase::admob::AdRequest request = GetAdRequest(); + WaitForCompletion(banner->LoadAd(request), "LoadAd"); + + std::vector + expected_presentation_states; + int expected_num_bounding_box_changes = 0; + + // Make the BannerView visible. + WaitForCompletion(banner->Show(), "Show 0"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + // Move to each of the six pre-defined positions. + + WaitForCompletion(banner->MoveTo(firebase::admob::BannerView::kPositionTop), + "MoveTo(Top)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + WaitForCompletion( + banner->MoveTo(firebase::admob::BannerView::kPositionTopLeft), + "MoveTo(TopLeft)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + WaitForCompletion( + banner->MoveTo(firebase::admob::BannerView::kPositionTopRight), + "MoveTo(TopRight)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + WaitForCompletion( + banner->MoveTo(firebase::admob::BannerView::kPositionBottom), + "Moveto(Bottom)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + WaitForCompletion( + banner->MoveTo(firebase::admob::BannerView::kPositionBottomLeft), + "MoveTo(BottomLeft)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + WaitForCompletion( + banner->MoveTo(firebase::admob::BannerView::kPositionBottomRight), + "MoveTo(BottomRight)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + // Move to some coordinates. + WaitForCompletion(banner->MoveTo(100, 300), "MoveTo(x0, y0)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + WaitForCompletion(banner->MoveTo(100, 400), "MoveTo(x1, y1)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + // Try hiding and showing the BannerView. + WaitForCompletion(banner->Hide(), "Hide 1"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateHidden); + + WaitForCompletion(banner->Show(), "Show 1"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + // Move again after hiding/showing. + WaitForCompletion(banner->MoveTo(100, 300), "MoveTo(x2, y2)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + WaitForCompletion(banner->MoveTo(100, 400), "Moveto(x3, y3)"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateVisibleWithAd); + expected_num_bounding_box_changes++; + + WaitForCompletion(banner->Hide(), "Hide 2"); + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateHidden); + + delete banner; + expected_presentation_states.push_back( + firebase::admob::BannerView::kPresentationStateHidden); + expected_num_bounding_box_changes++; + +#if defined(__ANDROID__) || TARGET_OS_IPHONE + // Ensure that we got all the presentation state changes. + EXPECT_EQ(banner_listener.presentation_states_, expected_presentation_states); + + // For the bounding box, check that we got the number of bounding box events + // we expect, since we don't know the exact bounding box coordinates to + // expect. + EXPECT_EQ(banner_listener.bounding_box_changes_.size(), + expected_num_bounding_box_changes); + + // As an extra check, all bounding boxes except the last should have the same + // size aspect ratio that we requested. For example if you requested a 320x50 + // banner, you can get one with the size 960x150. Use EXPECT_NEAR because the + // calculation can have a small bit of error. + double kAspectRatioAllowedError = 0.02; // Allow about 2% of error. + double expected_aspect_ratio = + static_cast(kBannerWidth) / static_cast(kBannerHeight); + for (int i = 0; i < banner_listener.bounding_box_changes_.size() - 1; ++i) { + double actual_aspect_ratio = + static_cast(banner_listener.bounding_box_changes_[i].width) / + static_cast(banner_listener.bounding_box_changes_[i].height); + EXPECT_NEAR(actual_aspect_ratio, expected_aspect_ratio, + kAspectRatioAllowedError) + << "Banner size " << banner_listener.bounding_box_changes_[i].width + << "x" << banner_listener.bounding_box_changes_[i].height + << " does not have the same aspect ratio as requested size " + << kBannerWidth << "x" << kBannerHeight << "."; + } + + // And finally, the last bounding box change, when the banner is deleted, + // should be (0,0,0,0). + EXPECT_TRUE(banner_listener.bounding_box_changes_.back().x == 0 && + banner_listener.bounding_box_changes_.back().y == 0 && + banner_listener.bounding_box_changes_.back().width == 0 && + banner_listener.bounding_box_changes_.back().height == 0); +#endif +} + +// A simple listener to help test changes to a InterstitialAd. +class TestInterstitialAdListener + : public firebase::admob::InterstitialAd::Listener { + public: + void OnPresentationStateChanged( + firebase::admob::InterstitialAd* interstitial_ad, + firebase::admob::InterstitialAd::PresentationState state) override { + presentation_states_.push_back(state); + } + std::vector + presentation_states_; +}; + +TEST_F(FirebaseAdMobTest, TestInterstitialAd) { + TEST_REQUIRES_USER_INTERACTION; + + firebase::admob::InterstitialAd* interstitial = + new firebase::admob::InterstitialAd(); + WaitForCompletion(interstitial->Initialize(app_framework::GetWindowContext(), + kInterstitialAdUnit), + "Initialize"); + TestInterstitialAdListener interstitial_listener; + interstitial->SetListener(&interstitial_listener); + + firebase::admob::AdRequest request = GetAdRequest(); + // When the InterstitialAd is initialized, load an ad. + WaitForCompletion(interstitial->LoadAd(request), "LoadAd"); + std::vector + expected_presentation_states; + + WaitForCompletion(interstitial->Show(), "Show"); + expected_presentation_states.push_back( + firebase::admob::InterstitialAd::PresentationState:: + kPresentationStateCoveringUI); + // Wait for the user to close the interstitial ad. + while (interstitial->presentation_state() != + firebase::admob::InterstitialAd::PresentationState:: + kPresentationStateHidden) { + app_framework::ProcessEvents(1000); + } + expected_presentation_states.push_back( + firebase::admob::InterstitialAd::PresentationState:: + kPresentationStateHidden); +#if defined(__ANDROID__) || TARGET_OS_IPHONE + EXPECT_EQ(interstitial_listener.presentation_states_, + expected_presentation_states); +#endif + delete interstitial; +} + +// A simple listener to help test changes to rewarded video state. +class TestRewardedVideoListener + : public firebase::admob::rewarded_video::Listener { + public: + TestRewardedVideoListener() { got_reward_ = false; } + void OnRewarded(firebase::admob::rewarded_video::RewardItem reward) override { + got_reward_ = true; + reward_type_ = reward.reward_type; + reward_amount_ = reward.amount; + } + void OnPresentationStateChanged( + firebase::admob::rewarded_video::PresentationState state) override { + presentation_states_.push_back(state); + } + bool got_reward_; + std::string reward_type_; + float reward_amount_; + std::vector + presentation_states_; +}; + +TEST_F(FirebaseAdMobTest, TestRewardedVideoAd) { + TEST_REQUIRES_USER_INTERACTION; + + namespace rewarded_video = firebase::admob::rewarded_video; + WaitForCompletion(rewarded_video::Initialize(), "Initialize"); + + TestRewardedVideoListener rewarded_listener; + rewarded_video::SetListener(&rewarded_listener); + + firebase::admob::AdRequest request = GetAdRequest(); + WaitForCompletion(rewarded_video::LoadAd(kRewardedVideoAdUnit, request), + "LoadAd"); + + std::vector expected_presentation_states; + + WaitForCompletion(rewarded_video::Show(app_framework::GetWindowContext()), + "Show"); + + expected_presentation_states.push_back( + rewarded_video::PresentationState::kPresentationStateCoveringUI); + expected_presentation_states.push_back( + rewarded_video::PresentationState::kPresentationStateVideoHasStarted); + + // Wait a moment, then pause, then resume. + ProcessEvents(1000); + WaitForCompletion(rewarded_video::Pause(), "Pause"); + ProcessEvents(1000); + WaitForCompletion(rewarded_video::Resume(), "Resume"); + +#if defined(__ANDROID__) || TARGET_OS_IPHONE + // Wait for video to complete. + while ( + rewarded_listener.presentation_states_.back() != + rewarded_video::PresentationState::kPresentationStateVideoHasCompleted) { + ProcessEvents(1000); + } + expected_presentation_states.push_back( + rewarded_video::PresentationState::kPresentationStateVideoHasCompleted); + + EXPECT_TRUE(rewarded_listener.got_reward_); + EXPECT_NE(rewarded_listener.reward_type_, ""); + EXPECT_NE(rewarded_listener.reward_amount_, 0); + LogDebug("Got reward: %.02f %s", rewarded_listener.reward_amount_, + rewarded_listener.reward_type_.c_str()); + + EXPECT_EQ(rewarded_listener.presentation_states_, + expected_presentation_states); +#endif + rewarded_video::Destroy(); +} + +#if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +// Test runs & compiles for phones only. + +struct ThreadArgs { + firebase::admob::BannerView* banner; + sem_t* semaphore; +}; + +static void* DeleteBannerViewOnSignal(void* args) { + ThreadArgs* thread_args = static_cast(args); + sem_wait(thread_args->semaphore); + delete thread_args->banner; + return nullptr; +} + +TEST_F(FirebaseAdMobTest, TestBannerViewMultithreadDeletion) { + SKIP_TEST_ON_DESKTOP; + + static const int kBannerWidth = 320; + static const int kBannerHeight = 50; + + firebase::admob::AdSize banner_ad_size; + banner_ad_size.ad_size_type = firebase::admob::kAdSizeStandard; + banner_ad_size.width = kBannerWidth; + banner_ad_size.height = kBannerHeight; + + for (int i = 0; i < 5; ++i) { + firebase::admob::BannerView* banner = new firebase::admob::BannerView(); + WaitForCompletion(banner->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + sem_t semaphore; + sem_init(&semaphore, 0, 1); + + ThreadArgs args = {banner, &semaphore}; + + pthread_t t1; + int err = pthread_create(&t1, nullptr, &DeleteBannerViewOnSignal, &args); + EXPECT_EQ(err, 0); + + banner->Destroy(); + sem_post(&semaphore); + + // Blocks until DeleteBannerViewOnSignal function is done. + void* result = nullptr; + err = pthread_join(t1, &result); + + EXPECT_EQ(err, 0); + EXPECT_EQ(result, nullptr); + + sem_destroy(&semaphore); + } +} +#endif // #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && + // TARGET_OS_IPHONE) + +} // namespace firebase_testapp_automated diff --git a/analytics/integration_test/AndroidManifest.xml b/analytics/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..9acf51d9d2 --- /dev/null +++ b/analytics/integration_test/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/analytics/integration_test/CMakeLists.txt b/analytics/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..7d2609824d --- /dev/null +++ b/analytics/integration_test/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_analytics firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/analytics/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/analytics/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/analytics/integration_test/Images.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/analytics/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/analytics/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/analytics/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/analytics/integration_test/Info.plist b/analytics/integration_test/Info.plist new file mode 100644 index 0000000000..3e13e24ba6 --- /dev/null +++ b/analytics/integration_test/Info.plist @@ -0,0 +1,42 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.ios.analytics.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + firebase-game-loop + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/analytics/integration_test/LaunchScreen.storyboard b/analytics/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/analytics/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/analytics/integration_test/LibraryManifest.xml b/analytics/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..5e886a778c --- /dev/null +++ b/analytics/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/analytics/integration_test/Podfile b/analytics/integration_test/Podfile new file mode 100644 index 0000000000..5ab82b3642 --- /dev/null +++ b/analytics/integration_test/Podfile @@ -0,0 +1,15 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase Analytics test application. + +target 'integration_test' do + pod 'Firebase/Analytics', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/analytics/integration_test/build.gradle b/analytics/integration_test/build.gradle new file mode 100644 index 0000000000..a1d574b5e4 --- /dev/null +++ b/analytics/integration_test/build.gradle @@ -0,0 +1,76 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.android.analytics.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + analytics +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/analytics/integration_test/googletest.cmake b/analytics/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/analytics/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/analytics/integration_test/gradle/wrapper/gradle-wrapper.jar b/analytics/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/analytics/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/analytics/integration_test/gradle/wrapper/gradle-wrapper.properties b/analytics/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/analytics/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/analytics/integration_test/gradlew b/analytics/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/analytics/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/analytics/integration_test/gradlew.bat b/analytics/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/analytics/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/analytics/integration_test/integration_test.xcodeproj/project.pbxproj b/analytics/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d1d6b852d1 --- /dev/null +++ b/analytics/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/analytics/integration_test/proguard.pro b/analytics/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/analytics/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/analytics/integration_test/readme.md b/analytics/integration_test/readme.md new file mode 100644 index 0000000000..18599e5952 --- /dev/null +++ b/analytics/integration_test/readme.md @@ -0,0 +1,184 @@ +Firebase Analytics Quickstart +============================== + +The Firebase Analytics Sample demonstrates logging a range of different events +using the Firebase Analytics C++ SDK. The application has no user interface and +simply logs actions it's performing to the console. + +Introduction +------------ + +- [Read more about Firebase Analytics](https://firebase.google.com/docs/analytics/) + +Building and Running the sample +------------------------------- + +### iOS + - Link your iOS app to the Firebase libraries. + - Get CocoaPods version 1 or later by running, + ``` + sudo gem install cocoapods --pre + ``` + - From the sample directory, install the CocoaPods listed in the Podfile + by running, + ``` + pod install + ``` + - Open the generated Xcode workspace (which now has the CocoaPods), + ``` + open sample.xcworkspace + ``` + - For further details please refer to the + [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). + - Register your iOS app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), and attach + your iOS app to it. + - You can use "com.google.ios.analytics.testapp" as the iOS Bundle ID + while you're testing. You can omit App Store ID while testing. + - Add the GoogleService-Info.plist that you downloaded from Firebase + console to the sample directory. This file identifies your iOS app + to the Firebase backend. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Add the following frameworks from the Firebase C++ SDK to the project: + - frameworks/ios/universal/firebase.framework + - frameworks/ios/universal/firebase\_analytics.framework + - You will need to either, + 1. Check "Copy items if needed" when adding the frameworks, or + 2. Add the framework path in "Framework Search Paths" + - e.g. If you downloaded the Firebase C++ SDK to + `/Users/me/firebase_cpp_sdk`, + then you would add the path + `/Users/me/firebase_cpp_sdk/frameworks/ios/universal`. + - To add the path, in XCode, select your project in the project + navigator, then select your target in the main window. + Select the "Build Settings" tab, and click "All" to see all + the build settings. Scroll down to "Search Paths", and add + your path to "Framework Search Paths". + - In XCode, build & run the sample on an iOS device or simulator. + - The sample has no user interface. The output of the app can be viewed + via the console. In Xcode, select + "View --> Debug Area --> Activate Console" from the menu. + - After 5 hours, data should be visible in the Firebase Console under the + "Analytics" tab accessible from + [https://firebase.google.com/console/](https://firebase.google.com/console/). + +### Android + - Register your Android app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), and attach + your Android app to it. + - You can use "com.google.android.analytics.testapp" as the Package Name + while you're testing. + - To [generate a SHA1](https://developers.google.com/android/guides/client-auth) + run this command on Mac and Linux, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore + ``` + or this command on Windows, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore + ``` + - If keytool reports that you do not have a debug.keystore, you can + [create one with](http://developer.android.com/tools/publishing/app-signing.html#signing-manually), + ``` + keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" + ``` + - Add the `google-services.json` file that you downloaded from Firebase + console to the sample directory. This file identifies your + Android app to the Firebase backend. + - For further details please refer to the + [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the location of the Firebase C++ SDK by setting the + firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. + For example, in the project directory: + ``` + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + ``` + - Ensure the Android SDK and NDK locations are set in Android Studio. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. + - Open *build.gradle* in Android Studio. + - From the Android Studio launch menu, "Open an existing Android Studio + project", and select `build.gradle`. + - Install the SDK Platforms that Android Studio reports missing. + - Build the sample and run it on an Android device or emulator. + - The sample has no user interface. The output of the app can be viewed + in the logcat output of Android studio or by running "adb logcat" from + the command line. + - After 5 hours, data should be visible in the Firebase Console under the + "Analytics" tab accessible from + [https://firebase.google.com/console/](https://firebase.google.com/console/). + +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the sample directory. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the root directory of the sample. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the sample with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the sample directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the sample, by either opening the generated project file based on + the platform, or running, + ``` + cmake --build . + ``` + - Execute the sample by running, + ``` + ./sample + ``` + Note that the executable might be under another directory, such as Debug. + - The sample has no user interface, but the output can be viewed via the + console. Note that Analytics uses a stubbed implementation on desktop, + so functionality is not expected. + +Support +------- + +[https://firebase.google.com/support/](https://firebase.google.com/support/) + +License +------- + +Copyright 2016 Google, Inc. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you 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. diff --git a/analytics/integration_test/res/layout/main.xml b/analytics/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/analytics/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/analytics/integration_test/res/values/strings.xml b/analytics/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..3375587120 --- /dev/null +++ b/analytics/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase Analytics Integration Test + diff --git a/analytics/integration_test/settings.gradle b/analytics/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/analytics/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/analytics/integration_test/src/integration_test.cc b/analytics/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..f629bdee07 --- /dev/null +++ b/analytics/integration_test/src/integration_test.cc @@ -0,0 +1,155 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include + +#include "app_framework.h" // NOLINT +#include "firebase/analytics.h" +#include "firebase/analytics/event_names.h" +#include "firebase/analytics/parameter_names.h" +#include "firebase/analytics/user_property_names.h" +#include "firebase/app.h" +#include "firebase/util.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using firebase_test_framework::FirebaseTest; + +class FirebaseAnalyticsTest : public FirebaseTest { + public: + static void SetUpTestSuite(); + static void TearDownTestSuite(); + + static firebase::App* shared_app_; +}; + +firebase::App* FirebaseAnalyticsTest::shared_app_; + +void FirebaseAnalyticsTest::SetUpTestSuite() { +#if defined(__ANDROID__) + shared_app_ = firebase::App::Create(app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + shared_app_ = firebase::App::Create(); +#endif // defined(__ANDROID__) + + firebase::analytics::Initialize(*shared_app_); +} + +void FirebaseAnalyticsTest::TearDownTestSuite() { + firebase::analytics::Terminate(); + delete shared_app_; + shared_app_ = nullptr; +} + +TEST_F(FirebaseAnalyticsTest, TestSetCollectionEnabled) { + // Can't confirm that these do anything but just run them all to ensure the + // app doesn't crash. + firebase::analytics::SetAnalyticsCollectionEnabled(true); + firebase::analytics::SetAnalyticsCollectionEnabled(false); + firebase::analytics::SetAnalyticsCollectionEnabled(true); +} + +TEST_F(FirebaseAnalyticsTest, TestSetSessionTimeoutDuraction) { + firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 5); + firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 15); + firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 30); +} + +TEST_F(FirebaseAnalyticsTest, TestGetAnalyticsInstanceID) { + firebase::Future future = + firebase::analytics::GetAnalyticsInstanceId(); + WaitForCompletion(future, "GetAnalyticsInstanceId"); + EXPECT_FALSE(future.result()->empty()); +} + +TEST_F(FirebaseAnalyticsTest, TestSetProperties) { + // Set the user's sign up method. + firebase::analytics::SetUserProperty( + firebase::analytics::kUserPropertySignUpMethod, "Google"); + // Set the user ID. + firebase::analytics::SetUserId("my_integration_test_user"); + + firebase::analytics::SetCurrentScreen( + "Firebase Analytics C++ integration test", "integration_test"); +} + +TEST_F(FirebaseAnalyticsTest, TestLogEvents) { + // Log an event with no parameters. + firebase::analytics::LogEvent(firebase::analytics::kEventLogin); + + // Log an event with a floating point parameter. + firebase::analytics::LogEvent("progress", "percent", 0.4f); + + // Log an event with an integer parameter. + firebase::analytics::LogEvent(firebase::analytics::kEventPostScore, + firebase::analytics::kParameterScore, 42); + + // Log an event with a string parameter. + firebase::analytics::LogEvent(firebase::analytics::kEventJoinGroup, + firebase::analytics::kParameterGroupID, + "spoon_welders"); +} + +TEST_F(FirebaseAnalyticsTest, TestLogEventWithMultipleParameters) { + const firebase::analytics::Parameter kLevelUpParameters[] = { + firebase::analytics::Parameter(firebase::analytics::kParameterLevel, 5), + firebase::analytics::Parameter(firebase::analytics::kParameterCharacter, + "mrspoon"), + firebase::analytics::Parameter("hit_accuracy", 3.14f), + }; + firebase::analytics::LogEvent( + firebase::analytics::kEventLevelUp, kLevelUpParameters, + sizeof(kLevelUpParameters) / sizeof(kLevelUpParameters[0])); +} + +#if !(TARGET_OS_IPHONE) +// Test is flakey on iPhone due to a known issue in iOS. See b/143656277. +TEST_F(FirebaseAnalyticsTest, TestResettingGivesNewInstanceId) { + firebase::Future future = + firebase::analytics::GetAnalyticsInstanceId(); + WaitForCompletion(future, "GetAnalyticsInstanceId"); + EXPECT_FALSE(future.result()->empty()); + std::string instance_id = *future.result(); + + firebase::analytics::ResetAnalyticsData(); + + future = firebase::analytics::GetAnalyticsInstanceId(); + WaitForCompletion(future, "GetAnalyticsInstanceId after ResetAnalyticsData"); + std::string new_instance_id = *future.result(); + EXPECT_FALSE(future.result()->empty()); + EXPECT_NE(instance_id, new_instance_id); +} +#endif // !(TARGET_OS_IPHONE) + +} // namespace firebase_testapp_automated diff --git a/app/integration_test/AndroidManifest.xml b/app/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..ffd3c1f9d0 --- /dev/null +++ b/app/integration_test/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/integration_test/CMakeLists.txt b/app/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..f27111df56 --- /dev/null +++ b/app/integration_test/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/app/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/app/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/app/integration_test/Images.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/app/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/app/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/app/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/app/integration_test/Info.plist b/app/integration_test/Info.plist new file mode 100644 index 0000000000..d938a81f9b --- /dev/null +++ b/app/integration_test/Info.plist @@ -0,0 +1,41 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.ios.analytics.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/app/integration_test/LaunchScreen.storyboard b/app/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/app/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/integration_test/LibraryManifest.xml b/app/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..855b13b5dc --- /dev/null +++ b/app/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/integration_test/Podfile b/app/integration_test/Podfile new file mode 100644 index 0000000000..2f9fa57fa2 --- /dev/null +++ b/app/integration_test/Podfile @@ -0,0 +1,15 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase App test application. + +target 'integration_test' do + pod 'Firebase/Analytics', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/app/integration_test/build.gradle b/app/integration_test/build.gradle new file mode 100644 index 0000000000..95ab16fd6c --- /dev/null +++ b/app/integration_test/build.gradle @@ -0,0 +1,76 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.android.analytics.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + app +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/app/integration_test/googletest.cmake b/app/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/app/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/app/integration_test/gradle/wrapper/gradle-wrapper.jar b/app/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/app/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/app/integration_test/gradle/wrapper/gradle-wrapper.properties b/app/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/app/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/app/integration_test/gradlew b/app/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/app/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/app/integration_test/gradlew.bat b/app/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/app/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/app/integration_test/integration_test.xcodeproj/project.pbxproj b/app/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d1d6b852d1 --- /dev/null +++ b/app/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/app/integration_test/proguard.pro b/app/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/app/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/app/integration_test/res/layout/main.xml b/app/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/app/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/integration_test/res/values/strings.xml b/app/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..15ff2a0621 --- /dev/null +++ b/app/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase App Integration Test + diff --git a/app/integration_test/settings.gradle b/app/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/app/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/app/integration_test/src/integration_test.cc b/app/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..c22acb8bec --- /dev/null +++ b/app/integration_test/src/integration_test.cc @@ -0,0 +1,68 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include + +#include "app_framework.h" // NOLINT +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using firebase_test_framework::FirebaseTest; + +class FirebaseAppTest : public FirebaseTest { + public: + FirebaseAppTest(); +}; + +FirebaseAppTest::FirebaseAppTest() { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +// For simplicity of test code, handle the Android-specific arguments here. +#if defined(__ANDROID__) +#define APP_CREATE_PARAMS \ + app_framework::GetJniEnv(), app_framework::GetActivity() +#else +#define APP_CREATE_PARAMS +#endif // defined(__ANDROID__) + +TEST_F(FirebaseAppTest, TestDefaultAppWithDefaultOptions) { + firebase::App* default_app; + default_app = firebase::App::Create(APP_CREATE_PARAMS); + EXPECT_NE(default_app, nullptr); + + delete default_app; + default_app = nullptr; +} + +} // namespace firebase_testapp_automated diff --git a/auth/integration_test/AndroidManifest.xml b/auth/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..c1c8a0c0ce --- /dev/null +++ b/auth/integration_test/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/auth/integration_test/CMakeLists.txt b/auth/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..12c9db3699 --- /dev/null +++ b/auth/integration_test/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_auth firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/auth/integration_test/Firebase_Cpp_Auth_Test_App_Dev.mobileprovision b/auth/integration_test/Firebase_Cpp_Auth_Test_App_Dev.mobileprovision new file mode 100644 index 0000000000..979b0856e6 Binary files /dev/null and b/auth/integration_test/Firebase_Cpp_Auth_Test_App_Dev.mobileprovision differ diff --git a/auth/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/auth/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/auth/integration_test/Images.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/auth/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/auth/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/auth/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/auth/integration_test/Info.plist b/auth/integration_test/Info.plist new file mode 100644 index 0000000000..d4e02ca97a --- /dev/null +++ b/auth/integration_test/Info.plist @@ -0,0 +1,54 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.google.FirebaseCppAuthTestApp.dev + CFBundleURLSchemes + + com.google.FirebaseCppAuthTestApp.dev + + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.53101460582-aosfj1hlbc89719t6qfian100u6hehh8 + firebase-game-loop + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIBackgroundModes + + fetch + + UILaunchStoryboardName + LaunchScreen + + diff --git a/auth/integration_test/LaunchScreen.storyboard b/auth/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/auth/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/auth/integration_test/LibraryManifest.xml b/auth/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..b6faca73f6 --- /dev/null +++ b/auth/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/auth/integration_test/Podfile b/auth/integration_test/Podfile new file mode 100644 index 0000000000..42a0f26285 --- /dev/null +++ b/auth/integration_test/Podfile @@ -0,0 +1,15 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase Auth test application. + +target 'integration_test' do + pod 'Firebase/Auth', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/auth/integration_test/build.gradle b/auth/integration_test/build.gradle new file mode 100644 index 0000000000..abdf5361a8 --- /dev/null +++ b/auth/integration_test/build.gradle @@ -0,0 +1,76 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.android.auth.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/auth/integration_test/googletest.cmake b/auth/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/auth/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/auth/integration_test/gradle/wrapper/gradle-wrapper.jar b/auth/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/auth/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/auth/integration_test/gradle/wrapper/gradle-wrapper.properties b/auth/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/auth/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/auth/integration_test/gradlew b/auth/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/auth/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/auth/integration_test/gradlew.bat b/auth/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/auth/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/auth/integration_test/integration_test.entitlements b/auth/integration_test/integration_test.entitlements new file mode 100644 index 0000000000..9199daef18 --- /dev/null +++ b/auth/integration_test/integration_test.entitlements @@ -0,0 +1,10 @@ + + + + + application-identifier + $(AppIdentifierPrefix)$(CFBundleIdentifier) + aps-environment + development + + diff --git a/auth/integration_test/integration_test.xcodeproj/project.pbxproj b/auth/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..a3901f2c6a --- /dev/null +++ b/auth/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,376 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; + D6E7D43C22D51D9900EDBD35 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D6E7D43C22D51D9900EDBD35 /* UserNotifications.framework */, + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 1; + }; + com.apple.Push = { + enabled = 1; + }; + }; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseCppAuthTestApp.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseCppAuthTestApp.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/auth/integration_test/proguard.pro b/auth/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/auth/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/auth/integration_test/res/layout/main.xml b/auth/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/auth/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/auth/integration_test/res/values/strings.xml b/auth/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..cfd0d03516 --- /dev/null +++ b/auth/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase Auth Integration Test + diff --git a/auth/integration_test/settings.gradle b/auth/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/auth/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/auth/integration_test/src/integration_test.cc b/auth/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..fbfa5c496e --- /dev/null +++ b/auth/integration_test/src/integration_test.cc @@ -0,0 +1,1118 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/auth.h" +#include "firebase/auth/credential.h" +#include "firebase/auth/user.h" +#include "firebase/util.h" +#include "firebase/variant.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +// Set kCustomTestEmail and kCustomTestPassword if you want to test email and +// password login using a custom account you've already set up on your Firebase +// project. +static const char kCustomTestEmail[] = "put_custom_test_account_here@gmail.com"; +static const char kCustomTestPassword[] = ""; + +static const int kWaitIntervalMs = 300; // NOLINT +static const int kPhoneAuthCodeSendWaitMs = 600000; // NOLINT +static const int kPhoneAuthCompletionWaitMs = 8000; // NOLINT +static const int kPhoneAuthTimeoutMs = 0; // NOLINT + +// Set these in Firebase Console for your app. +static const char kPhoneAuthTestPhoneNumber[] = "+12345556789"; // NOLINT +static const char kPhoneAuthTestVerificationCode[] = "123456"; // NOLINT + +static const char kTestPassword[] = "testEmailPassword123"; +static const char kTestEmailBad[] = "bad.test.email@example.com"; +static const char kTestPasswordBad[] = "badTestPassword"; +static const char kTestIdTokenBad[] = "bad id token for testing"; +static const char kTestAccessTokenBad[] = "bad access token for testing"; +static const char kTestPasswordUpdated[] = "testpasswordupdated"; +static const char kTestIdProviderIdBad[] = "bad provider id for testing"; +static const char kTestServerAuthCodeBad[] = "bad server auth code"; // NOLINT + +using app_framework::LogDebug; +using app_framework::LogError; // NOLINT +using app_framework::LogInfo; +using app_framework::ProcessEvents; + +using firebase_test_framework::FirebaseTest; + +class FirebaseAuthTest : public FirebaseTest { + public: + FirebaseAuthTest(); + ~FirebaseAuthTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + // Initialize Firebase App and Firebase Auth. + void Initialize(); + // Shut down Firebase App and Firebase Auth. + void Terminate(); + + // Sign out of any user we were signed into. This is automatically called + // before and after every test. + void SignOut(); + + // Delete the current user if it's currently signed in. + void DeleteUser(); + + // Passthrough method to the base class's WaitForCompletion. + bool WaitForCompletion(firebase::Future future, const char* fn, + int expected_error = firebase::auth::kAuthErrorNone) { + return FirebaseTest::WaitForCompletion(future, fn, expected_error); + } + + // Passthrough method to the base class's WaitForCompletion. + bool WaitForCompletion(firebase::Future future, const char* fn, + int expected_error = firebase::auth::kAuthErrorNone) { + return FirebaseTest::WaitForCompletion(future, fn, expected_error); + } + + // Custom WaitForCompletion that checks if User matches afterwards. + bool WaitForCompletion(firebase::Future future, + const char* fn, + int expected_error = firebase::auth::kAuthErrorNone); + // Custom WaitForCompletion that checks if User matches afterwards. + bool WaitForCompletion(firebase::Future future, + const char* fn, + int expected_error = firebase::auth::kAuthErrorNone); + + // Custom WaitForCompletion that checks if User and Provider ID matches + // afterwards. + bool WaitForCompletion(firebase::Future future, + const char* fn, const std::string& provider_id); + + bool initialized_; + firebase::auth::Auth* auth_; +}; + +FirebaseAuthTest::FirebaseAuthTest() : initialized_(false), auth_(nullptr) { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +FirebaseAuthTest::~FirebaseAuthTest() { + // Must be cleaned up on exit. + assert(app_ == nullptr); + assert(auth_ == nullptr); +} + +void FirebaseAuthTest::SetUp() { + FirebaseTest::SetUp(); + Initialize(); + SignOut(); +} + +void FirebaseAuthTest::TearDown() { + SignOut(); + Terminate(); + FirebaseTest::TearDown(); +} + +void FirebaseAuthTest::Initialize() { + if (initialized_) return; + + InitializeApp(); + + LogDebug("Initializing Firebase Auth."); + + ::firebase::ModuleInitializer initializer; + initializer.Initialize(app_, &auth_, [](::firebase::App* app, void* target) { + LogDebug("Try to initialize Firebase Auth"); + firebase::InitResult result; + firebase::auth::Auth** auth_ptr = + reinterpret_cast(target); + *auth_ptr = firebase::auth::Auth::GetAuth(app, &result); + return result; + }); + + FirebaseTest::WaitForCompletion(initializer.InitializeLastResult(), + "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase Auth."); + + initialized_ = true; +} + +void FirebaseAuthTest::Terminate() { + if (!initialized_) return; + + if (auth_) { + LogDebug("Shutdown the Auth library."); + delete auth_; + auth_ = nullptr; + } + + TerminateApp(); + + initialized_ = false; + + ProcessEvents(100); +} + +bool FirebaseAuthTest::WaitForCompletion( + firebase::Future future, const char* fn, + int expected_error) { + bool succeeded = FirebaseTest::WaitForCompletion(future, fn, expected_error); + + if (succeeded) { + if (expected_error == ::firebase::auth::kAuthErrorNone) { + const firebase::auth::User* future_result_user = + future.result() ? *future.result() : nullptr; + const firebase::auth::User* auth_user = auth_->current_user(); + EXPECT_EQ(future_result_user, auth_user) + << "User returned by Future doesn't match User in Auth"; + return (future_result_user == auth_user); + } + } + return succeeded; +} + +bool FirebaseAuthTest::WaitForCompletion( + firebase::Future future, const char* fn, + int expected_error) { + bool succeeded = FirebaseTest::WaitForCompletion(future, fn, expected_error); + + if (succeeded) { + if (expected_error == ::firebase::auth::kAuthErrorNone) { + const firebase::auth::User* future_result_user = + (future.result() && future.result()->user) ? future.result()->user + : nullptr; + const firebase::auth::User* auth_user = auth_->current_user(); + EXPECT_EQ(future_result_user, auth_user) + << "User returned by Future doesn't match User in Auth"; + return (future_result_user == auth_user); + } + } + return succeeded; +} + +bool FirebaseAuthTest::WaitForCompletion( + firebase::Future future, const char* fn, + const std::string& provider_id) { + bool succeeded = FirebaseTest::WaitForCompletion(future, fn); + if (succeeded) { + const firebase::auth::SignInResult* result_ptr = future.result(); + EXPECT_NE(result_ptr->user, nullptr); + EXPECT_EQ(result_ptr->info.provider_id, provider_id); + } + return succeeded; +} + +void FirebaseAuthTest::SignOut() { + if (auth_ == nullptr) { + // Auth is not set up. + return; + } + if (auth_->current_user() == nullptr) { + // Already signed out. + return; + } + auth_->SignOut(); + // Wait for the sign-out to finish. + while (auth_->current_user() != nullptr) { + if (ProcessEvents(100)) break; + } + ProcessEvents(100); + EXPECT_EQ(auth_->current_user(), nullptr); +} + +void FirebaseAuthTest::DeleteUser() { + if (auth_ != nullptr && auth_->current_user() != nullptr) { + FirebaseTest::WaitForCompletion(auth_->current_user()->Delete(), + "Delete User"); + ProcessEvents(100); + } +} + +TEST_F(FirebaseAuthTest, TestInitialization) { + // Initialized in SetUp and terminated in TearDown. + EXPECT_NE(app_, nullptr); + EXPECT_NE(auth_, nullptr); +} + +TEST_F(FirebaseAuthTest, TestAnonymousSignin) { + // Test notification on SignIn(). + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + EXPECT_NE(auth_->current_user(), nullptr); + EXPECT_TRUE(auth_->current_user()->is_anonymous()); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestCredentialCopy) { + // --- Credential copy tests ------------------------------------------------- + { + firebase::auth::Credential email_cred = + firebase::auth::EmailAuthProvider::GetCredential(kCustomTestEmail, + kTestPassword); + firebase::auth::Credential facebook_cred = + firebase::auth::FacebookAuthProvider::GetCredential( + kTestAccessTokenBad); + + std::string email_provider = email_cred.provider(); + std::string facebook_provider = facebook_cred.provider(); + + // Test copy constructor. + firebase::auth::Credential cred_copy(email_cred); + EXPECT_EQ(cred_copy.provider(), email_provider); + // Test assignment operator. + cred_copy = facebook_cred; + EXPECT_EQ(cred_copy.provider(), facebook_provider); + } +} + +class TestAuthStateListener : public firebase::auth::AuthStateListener { + public: + virtual void OnAuthStateChanged(firebase::auth::Auth* auth) { // NOLINT + // Log the provider ID. + std::string provider = + auth->current_user() ? auth->current_user()->provider_id() : ""; + LogDebug("OnAuthStateChanged called, provider=%s", provider.c_str()); + if (auth_states_.empty() || auth_states_.back() != provider) { + // Only log unique events. + auth_states_.push_back(provider); + } + } + const std::vector& auth_states() { return auth_states_; } + + private: + std::vector auth_states_; +}; + +class TestIdTokenListener : public firebase::auth::IdTokenListener { + public: + virtual void OnIdTokenChanged(firebase::auth::Auth* auth) { // NOLINT + // Log the auth token (if available). + std::string token = ""; + if (auth->current_user()) { + firebase::Future token_future = + auth->current_user()->GetToken(false); + if (token_future.status() == firebase::kFutureStatusComplete) { + if (token_future.error() == 0) { + token = *token_future.result(); + } + } else { + token = "[in progress]"; + } + } + LogDebug("OnIdTokenChanged called, token=%s", token.c_str()); + if (token_states_.empty() || !token.empty() || + token_states_.back() != token) { + // Only log unique empty events. + token_states_.push_back(token); + } + } + + const std::vector& token_states() { return token_states_; } + + private: + std::vector token_states_; +}; + +using testing::AnyOf; +using testing::ElementsAre; +using testing::Not; +using testing::StrCaseEq; + +TEST_F(FirebaseAuthTest, TestTokensAndAuthStateListeners) { + TestAuthStateListener listener; + TestIdTokenListener token_listener; + auth_->AddAuthStateListener(&listener); + auth_->AddIdTokenListener(&token_listener); + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + // Get an initial token. + firebase::Future token_future = + auth_->current_user()->GetToken(false); + WaitForCompletion(token_future, "GetToken(false)"); + std::string first_token = *token_future.result(); + // Force a token refresh. + ProcessEvents(1000); + token_future = auth_->current_user()->GetToken(true); + WaitForCompletion(token_future, "GetToken(true)"); + EXPECT_NE(*token_future.result(), ""); + std::string second_token = *token_future.result(); + EXPECT_NE(first_token, second_token); + + DeleteUser(); + SignOut(); + auth_->RemoveAuthStateListener(&listener); + auth_->RemoveIdTokenListener(&token_listener); + // Providers should be blank, then Firebase, then blank. + EXPECT_THAT(listener.auth_states(), + ElementsAre("", StrCaseEq("Firebase"), "")); + // We should have blank, then two (or sometimes three) tokens, then blank. + EXPECT_THAT(token_listener.token_states(), + AnyOf(ElementsAre("", Not(""), Not(""), ""), + ElementsAre("", Not(""), Not(""), Not(""), ""))); +} + +static std::string GenerateEmailAddress() { + std::string email = + "random_user_" + + std::to_string(app_framework::GetCurrentTimeInMicroseconds()) + + "@gmail.com"; + LogDebug("Generated email address: %s", email.c_str()); + return email; +} + +TEST_F(FirebaseAuthTest, TestEmailAndPasswordSignin) { + std::string email = GenerateEmailAddress(); + // Register a random email and password. This signs us in as that user. + std::string password = kTestPassword; + firebase::Future create_user = + auth_->CreateUserWithEmailAndPassword(email.c_str(), password.c_str()); + WaitForCompletion(create_user, "CreateUserWithEmailAndPassword"); + EXPECT_NE(auth_->current_user(), nullptr); + // Sign out and log in using SignInWithCredential(EmailCredential). + SignOut(); + { + firebase::auth::Credential email_credential = + firebase::auth::EmailAuthProvider::GetCredential(email.c_str(), + password.c_str()); + WaitForCompletion(auth_->SignInWithCredential(email_credential), + "SignInWithCredential"); + EXPECT_NE(auth_->current_user(), nullptr); + } + // Sign out and log in using + // SignInAndRetrieveDataWithCredential(EmailCredential). + SignOut(); + { + firebase::auth::Credential email_credential = + firebase::auth::EmailAuthProvider::GetCredential(email.c_str(), + password.c_str()); + WaitForCompletion( + auth_->SignInAndRetrieveDataWithCredential(email_credential), + "SignAndRetrieveDataInWithCredential"); + EXPECT_NE(auth_->current_user(), nullptr); + } + SignOut(); + // Sign in with SignInWithEmailAndPassword values. + firebase::Future sign_in_user = + auth_->SignInWithEmailAndPassword(email.c_str(), password.c_str()); + WaitForCompletion(sign_in_user, "SignInWithEmailAndPassword"); + ASSERT_NE(auth_->current_user(), nullptr); + + // Then delete the account. + firebase::Future delete_user = auth_->current_user()->Delete(); + WaitForCompletion(delete_user, "Delete"); + firebase::Future invalid_sign_in_user = + auth_->SignInWithEmailAndPassword(email.c_str(), password.c_str()); + WaitForCompletion(invalid_sign_in_user, + "SignInWithEmailAndPassword (invalid user)", + firebase::auth::kAuthErrorUserNotFound); + EXPECT_EQ(auth_->current_user(), nullptr); +} + +TEST_F(FirebaseAuthTest, TestUpdateUserProfile) { + std::string email = GenerateEmailAddress(); + firebase::Future create_user = + auth_->CreateUserWithEmailAndPassword(email.c_str(), kTestPassword); + WaitForCompletion(create_user, "CreateUserWithEmailAndPassword"); + EXPECT_NE(auth_->current_user(), nullptr); + // Set some user profile properties. + firebase::auth::User* user = *create_user.result(); + const char kDisplayName[] = "Hello World"; + const char kPhotoUrl[] = "http://example.com/image.jpg"; + firebase::auth::User::UserProfile user_profile; + user_profile.display_name = kDisplayName; + user_profile.photo_url = kPhotoUrl; + firebase::Future update_profile = user->UpdateUserProfile(user_profile); + WaitForCompletion(update_profile, "UpdateUserProfile"); + EXPECT_EQ(user->display_name(), kDisplayName); + EXPECT_EQ(user->photo_url(), kPhotoUrl); + SignOut(); + WaitForCompletion( + auth_->SignInWithEmailAndPassword(email.c_str(), kTestPassword), + "SignInWithEmailAndPassword"); + EXPECT_EQ(user->display_name(), kDisplayName); + EXPECT_EQ(user->photo_url(), kPhotoUrl); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestUpdateEmailAndPassword) { + std::string email = GenerateEmailAddress(); + WaitForCompletion( + auth_->CreateUserWithEmailAndPassword(email.c_str(), kTestPassword), + "CreateUserWithEmailAndPassword"); + ASSERT_NE(auth_->current_user(), nullptr); + firebase::auth::User* user = auth_->current_user(); + + // Update the user's email and password. + const std::string new_email = "new_" + email; + WaitForCompletion(user->UpdateEmail(new_email.c_str()), "UpdateEmail"); + WaitForCompletion(user->UpdatePassword(kTestPasswordUpdated), + "UpdatePassword"); + + firebase::auth::Credential new_email_cred = + firebase::auth::EmailAuthProvider::GetCredential(new_email.c_str(), + kTestPasswordUpdated); + WaitForCompletion(user->Reauthenticate(new_email_cred), "Reauthenticate"); + EXPECT_NE(auth_->current_user(), nullptr); + + WaitForCompletion(user->SendEmailVerification(), "SendEmailVerification"); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestLinkAnonymousUserWithEmailCredential) { + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + ASSERT_NE(auth_->current_user(), nullptr); + firebase::auth::User* user = auth_->current_user(); + std::string email = GenerateEmailAddress(); + firebase::auth::Credential credential = + firebase::auth::EmailAuthProvider::GetCredential(email.c_str(), + kTestPassword); + WaitForCompletion(user->LinkAndRetrieveDataWithCredential(credential), + "LinkAndRetrieveDataWithCredential"); + WaitForCompletion(user->Unlink(credential.provider().c_str()), "Unlink"); + SignOut(); + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + EXPECT_NE(auth_->current_user(), nullptr); + std::string email1 = GenerateEmailAddress(); + firebase::auth::Credential credential1 = + firebase::auth::EmailAuthProvider::GetCredential(email1.c_str(), + kTestPassword); + WaitForCompletion(user->LinkWithCredential(credential1), + "LinkWithCredential 1"); + std::string email2 = GenerateEmailAddress(); + firebase::auth::Credential credential2 = + firebase::auth::EmailAuthProvider::GetCredential(email2.c_str(), + kTestPassword); + WaitForCompletion(user->LinkWithCredential(credential2), + "LinkWithCredential 2", + firebase::auth::kAuthErrorProviderAlreadyLinked); + WaitForCompletion(user->Unlink(credential.provider().c_str()), "Unlink 2"); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestLinkAnonymousUserWithBadCredential) { + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + ASSERT_NE(auth_->current_user(), nullptr); + firebase::auth::User* pre_link_user = auth_->current_user(); + firebase::auth::Credential twitter_cred = + firebase::auth::TwitterAuthProvider::GetCredential(kTestIdTokenBad, + kTestAccessTokenBad); + WaitForCompletion(pre_link_user->LinkWithCredential(twitter_cred), + "LinkWithCredential", + firebase::auth::kAuthErrorInvalidCredential); + // Ensure that user stays the same. + EXPECT_EQ(auth_->current_user(), pre_link_user); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestSignInWithBadEmailFails) { + WaitForCompletion( + auth_->SignInWithEmailAndPassword(kTestEmailBad, kTestPassword), + "SignInWithEmailAndPassword", firebase::auth::kAuthErrorUserNotFound); + EXPECT_EQ(auth_->current_user(), nullptr); +} + +TEST_F(FirebaseAuthTest, TestSignInWithBadPasswordFails) { + std::string email = GenerateEmailAddress(); + WaitForCompletion( + auth_->CreateUserWithEmailAndPassword(email.c_str(), kTestPassword), + "CreateUserWithEmailAndPassword"); + EXPECT_NE(auth_->current_user(), nullptr); + SignOut(); + WaitForCompletion( + auth_->SignInWithEmailAndPassword(email.c_str(), kTestPasswordBad), + "SignInWithEmailAndPassword", firebase::auth::kAuthErrorWrongPassword); + EXPECT_EQ(auth_->current_user(), nullptr); + SignOut(); + // Sign back in and delete the user. + WaitForCompletion( + auth_->SignInWithEmailAndPassword(email.c_str(), kTestPassword), + "SignInWithEmailAndPassword"); + EXPECT_NE(auth_->current_user(), nullptr); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestCreateUserWithExistingEmailFails) { + std::string email = GenerateEmailAddress(); + WaitForCompletion( + auth_->CreateUserWithEmailAndPassword(email.c_str(), kTestPassword), + "CreateUserWithEmailAndPassword 1"); + EXPECT_NE(auth_->current_user(), nullptr); + SignOut(); + WaitForCompletion( + auth_->CreateUserWithEmailAndPassword(email.c_str(), kTestPassword), + "CreateUserWithEmailAndPassword 2", + firebase::auth::kAuthErrorEmailAlreadyInUse); + EXPECT_EQ(auth_->current_user(), nullptr); + SignOut(); + // Try again with a different password. + WaitForCompletion( + auth_->CreateUserWithEmailAndPassword(email.c_str(), kTestPasswordBad), + "CreateUserWithEmailAndPassword 3", + firebase::auth::kAuthErrorEmailAlreadyInUse); + EXPECT_EQ(auth_->current_user(), nullptr); + SignOut(); + WaitForCompletion( + auth_->SignInWithEmailAndPassword(email.c_str(), kTestPassword), + "SignInWithEmailAndPassword"); + EXPECT_NE(auth_->current_user(), nullptr); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestSignInWithBadCredentials) { + // Get an anonymous user first. + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + ASSERT_NE(auth_->current_user(), nullptr); + // Hold on to the existing user, to make sure it is unchanged by bad signins. + firebase::auth::User* existing_user = auth_->current_user(); + // Test signing in with a variety of bad credentials. + WaitForCompletion(auth_->SignInWithCredential( + firebase::auth::FacebookAuthProvider::GetCredential( + kTestAccessTokenBad)), + "SignInWithCredential (Facebook)", + firebase::auth::kAuthErrorInvalidCredential); + // Ensure that failing to sign in with a credential doesn't modify the user. + EXPECT_EQ(auth_->current_user(), existing_user); + WaitForCompletion(auth_->SignInWithCredential( + firebase::auth::TwitterAuthProvider::GetCredential( + kTestIdTokenBad, kTestAccessTokenBad)), + "SignInWithCredential (Twitter)", + firebase::auth::kAuthErrorInvalidCredential); + EXPECT_EQ(auth_->current_user(), existing_user); + WaitForCompletion(auth_->SignInWithCredential( + firebase::auth::GitHubAuthProvider::GetCredential( + kTestAccessTokenBad)), + "SignInWithCredential (GitHub)", + firebase::auth::kAuthErrorInvalidCredential); + EXPECT_EQ(auth_->current_user(), existing_user); + WaitForCompletion(auth_->SignInWithCredential( + firebase::auth::GoogleAuthProvider::GetCredential( + kTestIdTokenBad, kTestAccessTokenBad)), + "SignInWithCredential (Google 1)", + firebase::auth::kAuthErrorInvalidCredential); + EXPECT_EQ(auth_->current_user(), existing_user); + WaitForCompletion(auth_->SignInWithCredential( + firebase::auth::GoogleAuthProvider::GetCredential( + kTestIdTokenBad, nullptr)), + "SignInWithCredential (Google 2)", + firebase::auth::kAuthErrorInvalidCredential); + EXPECT_EQ(auth_->current_user(), existing_user); + WaitForCompletion( + auth_->SignInWithCredential(firebase::auth::OAuthProvider::GetCredential( + kTestIdProviderIdBad, kTestIdTokenBad, kTestAccessTokenBad)), + "SignInWithCredential (OAuth)", firebase::auth::kAuthErrorFailure); + EXPECT_EQ(auth_->current_user(), existing_user); + +#if defined(__ANDROID__) + // Test Play Games sign-in on Android only. + WaitForCompletion(auth_->SignInWithCredential( + firebase::auth::PlayGamesAuthProvider::GetCredential( + kTestServerAuthCodeBad)), + "SignInWithCredential (Play Games)", + firebase::auth::kAuthErrorInvalidCredential); + EXPECT_EQ(auth_->current_user(), existing_user); +#endif // defined(__ANDROID__) + DeleteUser(); +} + +#if TARGET_OS_IPHONE +TEST_F(FirebaseAuthTest, TestGameCenterSignIn) { + // Test Game Center sign-in on iPhone only. + if (!firebase::auth::GameCenterAuthProvider::IsPlayerAuthenticated()) { + LogInfo("Not signed into Game Center, skipping test."); + GTEST_SKIP(); + return; + } + LogDebug("Signed in, testing Game Center authentication."); + firebase::Future credential_future = + firebase::auth::GameCenterAuthProvider::GetCredential(); + FirebaseTest::WaitForCompletion(credential_future, + "GameCenterAuthProvider::GetCredential()"); + + EXPECT_NE(credential_future.result(), nullptr); + if (credential_future.result()) { + WaitForCompletion(auth_->SignInWithCredential(*credential_future.result()), + "SignInWithCredential (Game Center)"); + } + DeleteUser(); +} +#endif // TARGET_OS_IPHONE + +TEST_F(FirebaseAuthTest, TestSendPasswordResetEmail) { + // Test Auth::SendPasswordResetEmail(). + std::string email = GenerateEmailAddress(); + WaitForCompletion( + auth_->CreateUserWithEmailAndPassword(email.c_str(), kTestPassword), + "CreateUserWithEmailAndPassword"); + EXPECT_NE(auth_->current_user(), nullptr); + SignOut(); + // Send to correct email. + WaitForCompletion(auth_->SendPasswordResetEmail(email.c_str()), + "SendPasswordResetEmail (good)"); + // Send to incorrect email. + WaitForCompletion(auth_->SendPasswordResetEmail(kTestEmailBad), + "SendPasswordResetEmail (bad)", + firebase::auth::kAuthErrorUserNotFound); + // Delete user now that we are done with it. + WaitForCompletion( + auth_->SignInWithEmailAndPassword(email.c_str(), kTestPassword), + "SignInWithEmailAndPassword"); + EXPECT_NE(auth_->current_user(), nullptr); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestWithCustomEmailAndPassword) { + if (strlen(kCustomTestEmail) == 0 || strlen(kCustomTestPassword) == 0) { + LogInfo( + "Skipping %s. To enable this test, set " + "kCustomTestEmail and kCustomTestPassword in integration_test.cc.", + test_info_->name()); + GTEST_SKIP(); + return; + } + firebase::Future sign_in_user = + auth_->SignInWithEmailAndPassword(kCustomTestEmail, kCustomTestPassword); + WaitForCompletion(sign_in_user, "SignInWithEmailAndPassword"); + EXPECT_NE(auth_->current_user(), nullptr); +} + +TEST_F(FirebaseAuthTest, TestAuthPersistenceWithAnonymousSignin) { + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + ASSERT_NE(auth_->current_user(), nullptr); + EXPECT_TRUE(auth_->current_user()->is_anonymous()); + Terminate(); + ProcessEvents(2000); + Initialize(); + EXPECT_NE(auth_, nullptr); + ASSERT_NE(auth_->current_user(), nullptr); + EXPECT_TRUE(auth_->current_user()->is_anonymous()); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestAuthPersistenceWithEmailSignin) { + std::string email = GenerateEmailAddress(); + WaitForCompletion( + auth_->CreateUserWithEmailAndPassword(email.c_str(), kTestPassword), + "CreateUserWithEmailAndPassword"); + ASSERT_NE(auth_->current_user(), nullptr); + EXPECT_FALSE(auth_->current_user()->is_anonymous()); + std::string prev_provider_id = auth_->current_user()->provider_id(); + // Save the old provider ID list so we can make sure it's the same once it's + // loaded again. + std::vector prev_provider_data_ids; + for (int i = 0; i < auth_->current_user()->provider_data().size(); i++) { + prev_provider_data_ids.push_back( + auth_->current_user()->provider_data()[i]->provider_id()); + } + Terminate(); + ProcessEvents(2000); + Initialize(); + EXPECT_NE(auth_, nullptr); + ASSERT_NE(auth_->current_user(), nullptr); + EXPECT_FALSE(auth_->current_user()->is_anonymous()); + // Make sure the provider IDs are the same as they were before. + EXPECT_EQ(auth_->current_user()->provider_id(), prev_provider_id); + std::vector loaded_provider_data_ids; + for (int i = 0; i < auth_->current_user()->provider_data().size(); i++) { + loaded_provider_data_ids.push_back( + auth_->current_user()->provider_data()[i]->provider_id()); + } + EXPECT_EQ(loaded_provider_data_ids, prev_provider_data_ids); + + // Cleanup, ensure we are signed in as the user so we can delete it. + WaitForCompletion( + auth_->SignInWithEmailAndPassword(email.c_str(), kTestPassword), + "SignInWithEmailAndPassword"); + EXPECT_NE(auth_->current_user(), nullptr); + DeleteUser(); +} + +class PhoneListener : public firebase::auth::PhoneAuthProvider::Listener { + public: + PhoneListener() + : on_verification_complete_count_(0), + on_verification_failed_count_(0), + on_code_sent_count_(0), + on_code_auto_retrieval_time_out_count_(0) {} + + void OnVerificationCompleted(firebase::auth::Credential credential) override { + LogDebug("PhoneListener: successful automatic verification."); + on_verification_complete_count_++; + credential_ = credential; + } + + void OnVerificationFailed(const std::string& error) override { + LogError("PhoneListener verification failed with error, %s", error.c_str()); + on_verification_failed_count_++; + } + + void OnCodeSent(const std::string& verification_id, + const firebase::auth::PhoneAuthProvider::ForceResendingToken& + force_resending_token) override { + LogDebug("PhoneListener: code sent. verification_id=%s", + verification_id.c_str()); + verification_id_ = verification_id; + force_resending_token_ = force_resending_token; + on_code_sent_count_++; + } + + void OnCodeAutoRetrievalTimeOut(const std::string& verification_id) override { + LogDebug("PhoneListener: auto retrieval timeout. verification_id=%s", + verification_id.c_str()); + verification_id_ = verification_id; + on_code_auto_retrieval_time_out_count_++; + } + + const std::string& verification_id() const { return verification_id_; } + const firebase::auth::PhoneAuthProvider::ForceResendingToken& + force_resending_token() const { + return force_resending_token_; + } + int on_verification_complete_count() const { + return on_verification_complete_count_; + } + int on_verification_failed_count() const { + return on_verification_failed_count_; + } + int on_code_sent_count() const { return on_code_sent_count_; } + int on_code_auto_retrieval_time_out_count() const { + return on_code_auto_retrieval_time_out_count_; + } + + // Helper functions for workflow. + bool waiting_to_send_code() { + return on_verification_complete_count() == 0 && + on_verification_failed_count() == 0 && on_code_sent_count() == 0; + } + + bool waiting_for_verification_id() { + return on_verification_complete_count() == 0 && + on_verification_failed_count() == 0 && + on_code_auto_retrieval_time_out_count() == 0; + } + + firebase::auth::Credential credential() { return credential_; } + + private: + std::string verification_id_; + firebase::auth::PhoneAuthProvider::ForceResendingToken force_resending_token_; + firebase::auth::Credential credential_; + int on_verification_complete_count_; + int on_verification_failed_count_; + int on_code_sent_count_; + int on_code_auto_retrieval_time_out_count_; +}; + +TEST_F(FirebaseAuthTest, TestPhoneAuth) { + SKIP_TEST_ON_DESKTOP; + +#if TARGET_OS_IPHONE + // Note: This test requires interactivity on iOS, as it displays a CAPTCHA. + TEST_REQUIRES_USER_INTERACTION; +#endif // TARGET_OS_IPHONE + { + firebase::auth::PhoneAuthProvider& phone_provider = + firebase::auth::PhoneAuthProvider::GetInstance(auth_); + LogDebug("Creating listener."); + PhoneListener listener; + LogDebug("Calling VerifyPhoneNumber."); + phone_provider.VerifyPhoneNumber(kPhoneAuthTestPhoneNumber, + kPhoneAuthTimeoutMs, nullptr, &listener); + // Wait for OnCodeSent() callback. + int wait_ms = 0; + LogDebug("Waiting for code send."); + while (listener.waiting_to_send_code()) { + if (wait_ms > kPhoneAuthCodeSendWaitMs) break; + ProcessEvents(kWaitIntervalMs); + wait_ms += kWaitIntervalMs; + } + EXPECT_EQ(listener.on_verification_failed_count(), 0); + LogDebug("Waiting for verification ID."); + // Wait for the listener to have a verification ID. + wait_ms = 0; + while (listener.waiting_for_verification_id()) { + if (wait_ms > kPhoneAuthCompletionWaitMs) break; + ProcessEvents(kWaitIntervalMs); + wait_ms += kWaitIntervalMs; + } + if (listener.on_verification_complete_count() > 0) { + LogDebug("Signing in with automatic verification code."); + WaitForCompletion(auth_->SignInWithCredential(listener.credential()), + "SignInWithCredential(PhoneCredential) automatic"); + } else if (listener.on_verification_failed_count() > 0) { + FAIL() << "Automatic verification failed."; + } else { + // Did not automatically verify, submit verification code manually. + EXPECT_GT(listener.on_code_auto_retrieval_time_out_count(), 0); + EXPECT_NE(listener.verification_id(), ""); + LogDebug("Signing in with verification code."); + const firebase::auth::Credential phone_credential = + phone_provider.GetCredential(listener.verification_id().c_str(), + kPhoneAuthTestVerificationCode); + + WaitForCompletion(auth_->SignInWithCredential(phone_credential), + "SignInWithCredential(PhoneCredential)"); + } + } + ProcessEvents(1000); + DeleteUser(); +} + +#if defined(ENABLE_OAUTH_TESTS) +// SignInWithProvider +TEST_F(FirebaseAuthTest, TestSuccessfulSignInFederatedProviderNoScopes) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data( + provider_id, /*scopes=*/{}, /*custom_parameters=*/{{"req_id", "1234"}}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->SignInWithProvider(&provider); + WaitForCompletion(sign_in_future, "SignInWithProvider", provider_id); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, + TestSuccessfulSignInFederatedProviderNoScopesNoCustomParameters) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data( + provider_id, /*scopes=*/{}, /*custom_parameters=*/{}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->SignInWithProvider(&provider); + WaitForCompletion(sign_in_future, "SignInWithProvider", provider_id); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestSuccessfulSignInFederatedProvider) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data( + provider_id, + /*scopes=*/{"https://www.googleapis.com/auth/fitness.activity.read"}, + /*custom_parameters=*/{{"req_id", "1234"}}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->SignInWithProvider(&provider); + WaitForCompletion(sign_in_future, "SignInWithProvider", provider_id); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestSignInFederatedProviderBadProviderIdFails) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + firebase::auth::FederatedOAuthProviderData provider_data( + /*provider=*/"MadeUpProvider", + /*scopes=*/{"https://www.googleapis.com/auth/fitness.activity.read"}, + /*custom_parameters=*/{{"req_id", "5321"}}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->SignInWithProvider(&provider); + WaitForCompletion(sign_in_future, "SignInWithProvider", + firebase::auth::kAuthErrorInvalidProviderId); +} + +// ReauthenticateWithProvider +TEST_F(FirebaseAuthTest, TestSuccessfulReauthenticateWithProvider) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data( + provider_id, + /*scopes=*/{"https://www.googleapis.com/auth/fitness.activity.read"}, + /*custom_parameters=*/{{"req_id", "1234"}}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->SignInWithProvider(&provider); + if (WaitForCompletion(sign_in_future, "SignInWithProvider", provider_id)) { + WaitForCompletion( + sign_in_future.result()->user->ReauthenticateWithProvider(&provider), + "ReauthenticateWithProvider", provider_id); + } + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestSuccessfulReauthenticateWithProviderNoScopes) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data( + provider_id, /*scopes=*/{}, /*custom_parameters=*/{{"req_id", "1234"}}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->SignInWithProvider(&provider); + if (WaitForCompletion(sign_in_future, "SignInWithProvider", provider_id)) { + WaitForCompletion( + sign_in_future.result()->user->ReauthenticateWithProvider(&provider), + "ReauthenticateWithProvider", provider_id); + } + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, + TestSuccessfulReauthenticateWithProviderNoScopesNoCustomParameters) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data( + provider_id, /*scopes=*/{}, /*custom_parameters=*/{}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->SignInWithProvider(&provider); + if (WaitForCompletion(sign_in_future, "SignInWithProvider", provider_id)) { + WaitForCompletion( + sign_in_future.result()->user->ReauthenticateWithProvider(&provider), + "ReauthenticateWithProvider", provider_id); + } + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestReauthenticateWithProviderBadProviderIdFails) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data(provider_id); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->SignInWithProvider(&provider); + if (WaitForCompletion(sign_in_future, "SignInWithProvider", provider_id)) { + provider_data.provider_id = "MadeUpProvider"; + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future reauth_future = + auth_->current_user()->ReauthenticateWithProvider(&provider); + WaitForCompletion(reauth_future, "ReauthenticateWithProvider", + firebase::auth::kAuthErrorInvalidProviderId); + } + DeleteUser(); +} + +// LinkWithProvider +TEST_F(FirebaseAuthTest, TestSuccessfulLinkFederatedProviderNoScopes) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + ASSERT_NE(auth_->current_user(), nullptr); + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data( + provider_id, /*scopes=*/{}, /*custom_parameters=*/{{"req_id", "1234"}}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->current_user()->LinkWithProvider(&provider); + WaitForCompletion(sign_in_future, "LinkWithProvider", provider_id); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, + TestSuccessfulLinkFederatedProviderNoScopesNoCustomParameters) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + ASSERT_NE(auth_->current_user(), nullptr); + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data( + provider_id, /*scopes=*/{}, /*custom_parameters=*/{}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->current_user()->LinkWithProvider(&provider); + WaitForCompletion(sign_in_future, "LinkWithProvider", provider_id); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestSuccessfulLinkFederatedProvider) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + ASSERT_NE(auth_->current_user(), nullptr); + const std::string provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + firebase::auth::FederatedOAuthProviderData provider_data( + provider_id, + /*scopes=*/{"https://www.googleapis.com/auth/fitness.activity.read"}, + /*custom_parameters=*/{{"req_id", "1234"}}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->current_user()->LinkWithProvider(&provider); + WaitForCompletion(sign_in_future, "LinkWithProvider", provider_id); + DeleteUser(); +} + +TEST_F(FirebaseAuthTest, TestLinkFederatedProviderBadProviderIdFails) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + WaitForCompletion(auth_->SignInAnonymously(), "SignInAnonymously"); + ASSERT_NE(auth_->current_user(), nullptr); + firebase::auth::FederatedOAuthProviderData provider_data( + /*provider=*/"MadeUpProvider", + /*scopes=*/{"https://www.googleapis.com/auth/fitness.activity.read"}, + /*custom_parameters=*/{{"req_id", "1234"}}); + firebase::auth::FederatedOAuthProvider provider(provider_data); + firebase::Future sign_in_future = + auth_->current_user()->LinkWithProvider(&provider); + WaitForCompletion(sign_in_future, "LinkWithProvider", + firebase::auth::kAuthErrorInvalidProviderId); + DeleteUser(); +} + +#endif // defined(ENABLE_OAUTH_TESTS) + +} // namespace firebase_testapp_automated diff --git a/database/integration_test/AndroidManifest.xml b/database/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..4a470ed5fd --- /dev/null +++ b/database/integration_test/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/database/integration_test/CMakeLists.txt b/database/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..29638cae20 --- /dev/null +++ b/database/integration_test/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32 iphlpapi psapi userenv shell32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_database firebase_auth firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/database/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/database/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/database/integration_test/Images.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/database/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/database/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/database/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/database/integration_test/Info.plist b/database/integration_test/Info.plist new file mode 100644 index 0000000000..a21836a7b2 --- /dev/null +++ b/database/integration_test/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.firebase.cpp.database.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + firebase-game-loop + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/database/integration_test/LaunchScreen.storyboard b/database/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/database/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/database/integration_test/LibraryManifest.xml b/database/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..edd412c1b8 --- /dev/null +++ b/database/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/database/integration_test/Podfile b/database/integration_test/Podfile new file mode 100644 index 0000000000..1094de283a --- /dev/null +++ b/database/integration_test/Podfile @@ -0,0 +1,16 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase Realtime Database test application. + +target 'integration_test' do + pod 'Firebase/Database', '6.24.0' + pod 'Firebase/Auth', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/database/integration_test/build.gradle b/database/integration_test/build.gradle new file mode 100644 index 0000000000..f7f6059178 --- /dev/null +++ b/database/integration_test/build.gradle @@ -0,0 +1,77 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.firebase.cpp.database.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth + database +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/database/integration_test/googletest.cmake b/database/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/database/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/database/integration_test/gradle/wrapper/gradle-wrapper.jar b/database/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/database/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/database/integration_test/gradle/wrapper/gradle-wrapper.properties b/database/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/database/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/database/integration_test/gradlew b/database/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/database/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/database/integration_test/gradlew.bat b/database/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/database/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/database/integration_test/integration_test.xcodeproj/project.pbxproj b/database/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d1d6b852d1 --- /dev/null +++ b/database/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/database/integration_test/proguard.pro b/database/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/database/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/database/integration_test/readme.md b/database/integration_test/readme.md new file mode 100644 index 0000000000..6b9efa6fcd --- /dev/null +++ b/database/integration_test/readme.md @@ -0,0 +1,215 @@ +Cloud Storage for Firebase Quickstart +======================== + +The Cloud Storage for Firebase Integration Test (integration test) demonstrates +Cloud Storage operations with the Firebase C++ SDK for Cloud Storage. +The application has no user interface and simply logs actions it's performing +to the console. + +The integration test uses the GoogleTest testing framework to perform a variety +of tests on a live Firebase project. Tests include: + - Creating a firebase::App in a platform-specific way. The App holds + platform-specific context that's used by other Firebase APIs, and is a + central point for communication between the Cloud Storage C++ and + Firebase Auth C++ libraries. + - Getting a pointer to firebase::Auth, and signs in anonymously. This allows the + integration test to access a Cloud Storage instance with authentication rules + enabled, which is the default setting in Firebase Console. + - Gets a StorageReference to a test-specific storage node, uses + StorageReference::Child() to create a child with a unique key based on the + current time in microseconds to work in, and gets a reference to that child, + which the integration test will use for the remainder of its actions. + - Uploads some sample files and reads them back to ensure the storage can be + read from and written to. + - Checks the Metadata of the uploaded and downloaded files to ensure they + return the expected values for things like size and date modified. + - Disconnects and then reconnects and verifies it still has access to the + files uploaded. + - Shuts down the Cloud Storage, Firebase Auth, and Firebase App systems, + ensuring the systems can be shut down in any order. + +Introduction +------------ + +- [Read more about Cloud Storage for Firebase](https://firebase.google.com/docs/storage/) + +Building and Running the integration test +----------------------------------------- + +### iOS + - Link your iOS app to the Firebase libraries. + - Get CocoaPods version 1 or later by running, + ``` + sudo gem install cocoapods --pre + ``` + - From the integration_tests/storage directory, install the CocoaPods listed + in the Podfile by running, + ``` + pod install + ``` + - Open the generated Xcode workspace (which now has the CocoaPods), + ``` + open integration_test.xcworkspace + ``` + - For further details please refer to the + [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). + - Register your iOS app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), and attach + your iOS app to it. + - You can use "com.google.firebase.cpp.storage.testapp" as the iOS Bundle + ID while you're testing. You can omit App Store ID while testing. + - Add the GoogleService-Info.plist that you downloaded from Firebase console + to the integration_test/storage directory. This file identifies your iOS + app to the Firebase backend. + - In the Firebase console for your app, select "Auth", then enable + "Anonymous". This will allow the integration test to use anonymous sign-in to + authenticate with Cloud Storage, which requires a signed-in user by + default (an anonymous user will suffice). + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Add the following frameworks from the Firebase C++ SDK to the project: + - frameworks/ios/universal/firebase.framework + - frameworks/ios/universal/firebase_auth.framework + - frameworks/ios/universal/firebase_storage.framework + - You will need to either, + 1. Check "Copy items if needed" when adding the frameworks, or + 2. Add the framework path in "Framework Search Paths" + - e.g. If you downloaded the Firebase C++ SDK to + `/Users/me/firebase_cpp_sdk`, + then you would add the path + `/Users/me/firebase_cpp_sdk/frameworks/ios/universal`. + - To add the path, in XCode, select your project in the project + navigator, then select your target in the main window. + Select the "Build Settings" tab, and click "All" to see all + the build settings. Scroll down to "Search Paths", and add + your path to "Framework Search Paths". + - In XCode, build & run the sample on an iOS device or simulator. + - The integration test has no interativity. The output of the app can be + viewed via the console or on the device's display. In Xcode, select "View + --> Debug Area --> Activate Console" from the menu to view the console. + +### Android + - Register your Android app with Firebase. + - Create a new app on + the [Firebase console](https://firebase.google.com/console/), and attach + your Android app to it. + - You can use "com.google.firebase.cpp.storage.testapp" as the Package + Name while you're testing. + - To + [generate a SHA1](https://developers.google.com/android/guides/client-auth) + run this command on Mac and Linux, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore + ``` + or this command on Windows, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore + ``` + - If keytool reports that you do not have a debug.keystore, you can + [create one with](http://developer.android.com/tools/publishing/app-signing.html#signing-manually), + ``` + keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" + ``` + - Add the `google-services.json` file that you downloaded from Firebase + console to the integration_test/storage directory. This file identifies + your Android app to the Firebase backend. + - In the Firebase console for your app, select "Auth", then enable + "Anonymous". This will allow the integration test to use anonymous sign-in + to authenticate with Cloud Storage, which requires a signed-in user by + default (an anonymous user will suffice). + - For further details please refer to the + [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the location of the Firebase C++ SDK by setting the + firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. + For example, in the project directory: + ``` + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + ``` + - Ensure the Android SDK and NDK locations are set in Android Studio. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. + - Open *build.gradle* in Android Studio. + - From the Android Studio launch menu, "Open an existing Android Studio + project", and select `build.gradle`. + - Install the SDK Platforms that Android Studio reports missing. + - Build the integration test and run it on an Android device or emulator. + - Once you've installed the SDKs in Android Studio, you can build the + integration test in Android Studio, or from the command-line by running + `./gradlew build`. + - The integration test has no interactive interface. The output of the app can + be viewed on the device's display, or in the logcat output of Android studio + or by running "adb logcat *:W android_main firebase" from the command line. + +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the integration_test/storage + directory. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the integration_test/storage directory . + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the integration test with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the integration_test/storage directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the integration test, by either opening the generated project file + based on the platform, or running, + ``` + cmake --build . + ``` + - Execute the integration test by running, + ``` + ./integration_test + ``` + Note that the executable might be under another directory, such as Debug. + - The integration test has no user interface, but the output can be viewed via + the console. + +Support +------- + +[https://firebase.google.com/support/](https://firebase.google.com/support/) + +License +------- + +Copyright 2016 Google, Inc. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you 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. diff --git a/database/integration_test/res/layout/main.xml b/database/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/database/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/database/integration_test/res/values/strings.xml b/database/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..e848e9c7ba --- /dev/null +++ b/database/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase Database Integration Test + diff --git a/database/integration_test/settings.gradle b/database/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/database/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/database/integration_test/src/integration_test.cc b/database/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..4d97c92003 --- /dev/null +++ b/database/integration_test/src/integration_test.cc @@ -0,0 +1,1204 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include +#include +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/auth.h" +#include "firebase/database.h" +#include "firebase/internal/platform.h" +#include "firebase/util.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using app_framework::LogDebug; +using app_framework::LogError; +using app_framework::LogInfo; + +using app_framework::ProcessEvents; +using firebase_test_framework::FirebaseTest; + +using testing::ElementsAre; +using testing::Key; +using testing::Not; +using testing::Pair; +using testing::PrintToString; +using testing::UnorderedElementsAre; + +const char kIntegrationTestRootPath[] = "integration_test_data"; + +// Returns true if the given given timestamp is within 15 minutes of the +// expected timestamp. The value compared against must be a Variant of type +// int64 representing milliseconds since the epoch. +MATCHER_P(TimestampIsNear, expected, + std::string("Timestamp ") + (negation ? "isn't " : "is ") + "near " + + PrintToString(expected)) { + if (!arg.is_int64()) { + *result_listener << "Variant must be of type Int64, but was type " + << firebase::Variant::TypeName(arg.type()); + return false; + } + + // As long as our timestamp is within 15 minutes, it's correct enough + // for our purposes. + const int64_t kAllowedTimeDifferenceMilliseconds = 1000L * 60L * 15L; + + int64_t time_difference = arg.int64_value() - expected; + + return time_difference <= kAllowedTimeDifferenceMilliseconds && + time_difference >= -kAllowedTimeDifferenceMilliseconds; +} + +TEST(TimestampIsNear, Matcher) { + int64_t kOneMinuteInMilliseconds = 1L * 60L * 1000L; + int64_t kTenMinutesInMilliseconds = 10L * 60L * 1000L; + int64_t kTwentyMinutesInMilliseconds = 20L * 60L * 1000L; + + int64_t base_time = 1234567890; + firebase::Variant current_time = base_time; + EXPECT_THAT(current_time, TimestampIsNear(base_time)); + + int64_t one_minute_later = base_time + kOneMinuteInMilliseconds; + EXPECT_THAT(current_time, TimestampIsNear(one_minute_later)); + int64_t one_minutes_earlier = base_time - kOneMinuteInMilliseconds; + EXPECT_THAT(current_time, TimestampIsNear(one_minutes_earlier)); + + int64_t ten_minutes_later = base_time + kTenMinutesInMilliseconds; + EXPECT_THAT(current_time, TimestampIsNear(ten_minutes_later)); + int64_t ten_minutes_earlier = base_time - kTenMinutesInMilliseconds; + EXPECT_THAT(current_time, TimestampIsNear(ten_minutes_earlier)); + + int64_t twenty_minutes_later = base_time + kTwentyMinutesInMilliseconds; + EXPECT_THAT(current_time, Not(TimestampIsNear(twenty_minutes_later))); + int64_t twenty_minutes_earlier = base_time - kTwentyMinutesInMilliseconds; + EXPECT_THAT(current_time, Not(TimestampIsNear(twenty_minutes_earlier))); + + // Wrong types. + EXPECT_THAT(firebase::Variant::Null(), Not(TimestampIsNear(0))); + EXPECT_THAT(firebase::Variant::False(), Not(TimestampIsNear(0))); + EXPECT_THAT(firebase::Variant::EmptyString(), Not(TimestampIsNear(0))); +} + +class FirebaseDatabaseTest : public FirebaseTest { + public: + FirebaseDatabaseTest(); + ~FirebaseDatabaseTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + // Initialize Firebase App, Firebase Auth, and Firebase Database. + void Initialize(); + // Shut down Firebase Database, Firebase Auth, and Firebase App. + void Terminate(); + // Sign in an anonymous user. + void SignIn(); + + firebase::database::DatabaseReference CreateWorkingPath( + bool suppress_cleanup = false); + + static bool first_time_; + + bool initialized_; + firebase::auth::Auth* auth_; + firebase::database::Database* database_; + + std::vector cleanup_paths_; +}; + +bool FirebaseDatabaseTest::first_time_ = true; + +FirebaseDatabaseTest::FirebaseDatabaseTest() + : initialized_(false), auth_(nullptr), database_(nullptr) { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +FirebaseDatabaseTest::~FirebaseDatabaseTest() { + // Must be cleaned up on exit. + assert(app_ == nullptr); + assert(auth_ == nullptr); + assert(database_ == nullptr); +} + +void FirebaseDatabaseTest::SetUp() { + FirebaseTest::SetUp(); + Initialize(); +} + +void FirebaseDatabaseTest::TearDown() { + // Delete the shared path, if there is one. + if (initialized_) { + if (!cleanup_paths_.empty() && database_ && app_) { + LogDebug("Cleaning up..."); + std::vector> cleanups; + cleanups.reserve(cleanup_paths_.size()); + for (int i = 0; i < cleanup_paths_.size(); ++i) { + cleanups.push_back(cleanup_paths_[i].RemoveValue()); + } + for (int i = 0; i < cleanups.size(); ++i) { + std::string cleanup_name = "Cleanup (" + cleanup_paths_[i].url() + ")"; + WaitForCompletion(cleanups[i], cleanup_name.c_str()); + } + cleanup_paths_.clear(); + } + Terminate(); + } + FirebaseTest::TearDown(); +} + +void FirebaseDatabaseTest::Initialize() { + if (initialized_) return; + + InitializeApp(); + + LogDebug("Initializing Firebase Auth and Firebase Database."); + + // 0th element has a reference to this object, the rest have the initializer + // targets. + void* initialize_targets[] = {&auth_, &database_}; + + const firebase::ModuleInitializer::InitializerFn initializers[] = { + [](::firebase::App* app, void* data) { + void** targets = reinterpret_cast(data); + LogDebug("Attempting to initialize Firebase Auth."); + ::firebase::InitResult result; + *reinterpret_cast<::firebase::auth::Auth**>(targets[0]) = + ::firebase::auth::Auth::GetAuth(app, &result); + return result; + }, + [](::firebase::App* app, void* data) { + void** targets = reinterpret_cast(data); + LogDebug("Attempting to initialize Firebase Database."); + ::firebase::InitResult result; + firebase::database::Database* database = + firebase::database::Database::GetInstance(app, &result); + *reinterpret_cast<::firebase::database::Database**>(targets[1]) = + database; + return result; + }}; + + ::firebase::ModuleInitializer initializer; + initializer.Initialize(app_, initialize_targets, initializers, + sizeof(initializers) / sizeof(initializers[0])); + + WaitForCompletion(initializer.InitializeLastResult(), "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase Auth and Firebase Database."); + + // The first time we initialize database, enable persistence on mobile. + // We need to do this here because on iOS you can only enable persistence + // before ANY FIRDatabase instances are used. + if (first_time_) { + database_->set_persistence_enabled(true); + first_time_ = false; + } + + initialized_ = true; +} + +void FirebaseDatabaseTest::Terminate() { + if (!initialized_) return; + + if (database_) { + LogDebug("Shutdown the Database library."); + delete database_; + database_ = nullptr; + } + if (auth_) { + LogDebug("Shutdown the Auth library."); + delete auth_; + auth_ = nullptr; + } + + TerminateApp(); + + initialized_ = false; + + ProcessEvents(100); +} + +void FirebaseDatabaseTest::SignIn() { + LogDebug("Signing in."); + firebase::Future sign_in_future = + auth_->SignInAnonymously(); + WaitForCompletion(sign_in_future, "SignInAnonymously"); + if (sign_in_future.error() != 0) { + FAIL() << "Ensure your application has the Anonymous sign-in provider " + "enabled in Firebase Console."; + } + ProcessEvents(100); +} + +firebase::database::DatabaseReference FirebaseDatabaseTest::CreateWorkingPath( + bool suppress_cleanup) { + auto ref = database_->GetReference(kIntegrationTestRootPath).PushChild(); + if (!suppress_cleanup) { + cleanup_paths_.push_back(ref); + } + return ref; +} + +// Test cases below. + +TEST_F(FirebaseDatabaseTest, TestInitializeAndTerminate) { + // Already tested via SetUp() and TearDown(). +} + +TEST_F(FirebaseDatabaseTest, TestSignIn) { + SignIn(); + EXPECT_NE(auth_->current_user(), nullptr); +} + +TEST_F(FirebaseDatabaseTest, TestCreateWorkingPath) { + SignIn(); + firebase::database::DatabaseReference working_path = CreateWorkingPath(); + LogInfo("Database URL: %s", working_path.url().c_str()); + EXPECT_TRUE(working_path.is_valid()); + EXPECT_FALSE(working_path.url().empty()); + EXPECT_EQ(working_path.url().find(database_->GetReference().url()), 0) + << "Working path URL (" << working_path.url() + << ") does not begin with root URL (" << database_->GetReference().url() + << ")"; +} + +static const char kSimpleString[] = "Some simple string"; +static const int kSimpleInt = 2; +static const int kSimplePriority = 100; +static const double kSimpleDouble = 3.4; +static const bool kSimpleBool = true; + +TEST_F(FirebaseDatabaseTest, TestSetAndGetSimpleValues) { + const char* test_name = test_info_->name(); + SignIn(); + firebase::database::DatabaseReference ref = CreateWorkingPath(); + + { + LogDebug("Setting values."); + firebase::Future f1 = + ref.Child(test_name).Child("String").SetValue(kSimpleString); + firebase::Future f2 = + ref.Child(test_name).Child("Int").SetValue(kSimpleInt); + firebase::Future f3 = + ref.Child(test_name).Child("Double").SetValue(kSimpleDouble); + firebase::Future f4 = + ref.Child(test_name).Child("Bool").SetValue(kSimpleBool); + firebase::Future f5 = + ref.Child(test_name) + .Child("Timestamp") + .SetValue(firebase::database::ServerTimestamp()); + firebase::Future f6 = + ref.Child(test_name) + .Child("IntAndPriority") + .SetValueAndPriority(kSimpleInt, kSimplePriority); + WaitForCompletion(f1, "SetSimpleString"); + WaitForCompletion(f2, "SetSimpleInt"); + WaitForCompletion(f3, "SetSimpleDouble"); + WaitForCompletion(f4, "SetSimpleBool"); + WaitForCompletion(f5, "SetSimpleTimestamp"); + WaitForCompletion(f6, "SetSimpleIntAndPriority"); + } + + // Get the values that we just set, and confirm that they match what we + // set them to. + { + LogDebug("Getting values."); + firebase::Future f1 = + ref.Child(test_name).Child("String").GetValue(); + firebase::Future f2 = + ref.Child(test_name).Child("Int").GetValue(); + firebase::Future f3 = + ref.Child(test_name).Child("Double").GetValue(); + firebase::Future f4 = + ref.Child(test_name).Child("Bool").GetValue(); + firebase::Future f5 = + ref.Child(test_name).Child("Timestamp").GetValue(); + firebase::Future f6 = + ref.Child(test_name).Child("IntAndPriority").GetValue(); + WaitForCompletion(f1, "GetSimpleString"); + WaitForCompletion(f2, "GetSimpleInt"); + WaitForCompletion(f3, "GetSimpleDouble"); + WaitForCompletion(f4, "GetSimpleBool"); + WaitForCompletion(f5, "GetSimpleTimestamp"); + WaitForCompletion(f6, "GetSimpleIntAndPriority"); + + // Get the current time to compare to the Timestamp. + int64_t current_time_milliseconds = + static_cast(time(nullptr)) * 1000L; + + EXPECT_EQ(f1.result()->value().AsString(), kSimpleString); + EXPECT_EQ(f2.result()->value().AsInt64(), kSimpleInt); + EXPECT_EQ(f3.result()->value().AsDouble(), kSimpleDouble); + EXPECT_EQ(f4.result()->value().AsBool(), kSimpleBool); + EXPECT_THAT(f5.result()->value().AsInt64(), + TimestampIsNear(current_time_milliseconds)); + EXPECT_EQ(f6.result()->value().AsInt64(), kSimpleInt); + EXPECT_EQ(f6.result()->priority().AsInt64(), kSimplePriority); + } +} + +// A ValueListener that expects a specific value to be set. +class ExpectValueListener : public firebase::database::ValueListener { + public: + explicit ExpectValueListener(const firebase::Variant& value_to_expect) + : value_to_expect_(value_to_expect), + value_changed_(false), + got_expected_value_(false) {} + void OnValueChanged( + const firebase::database::DataSnapshot& snapshot) override { + value_changed_ = true; + if (snapshot.value().AsString() == value_to_expect_.AsString()) { + got_expected_value_ = true; + } else { + LogError("ExpectValueListener did not receive the expected result."); + } + } + void OnCancelled(const firebase::database::Error& error_code, + const char* error_message) override { + LogError("ExpectValueListener canceled: %d: %s", error_code, error_message); + value_changed_ = true; + got_expected_value_ = false; + } + + bool WaitForExpectedValue() { + const int kWaitForValueSeconds = 10; + + for (int i = 0; i < kWaitForValueSeconds; i++) { + ProcessEvents(1000); + if (value_changed_) { + return got_expected_value_; + } + } + LogError("ExpectValueListener timed out."); + return got_expected_value_; + } + + private: + firebase::Variant value_to_expect_; + bool value_changed_; + bool got_expected_value_; +}; + +TEST_F(FirebaseDatabaseTest, TestReadingFromPersistanceWhileOffline) { + const char* test_name = test_info_->name(); + + SignIn(); + // database_->set_persistence_enabled(true); // Already set in Initialize(). + + firebase::database::DatabaseReference ref = CreateWorkingPath(true); + std::string working_url = ref.url(); + + // Write a value that we can test for. + const char* kPersistenceString = "Persistence Test!"; + WaitForCompletion(ref.Child(test_name).SetValue(kPersistenceString), + "SetValue (online)"); + + // Shut down the realtime database and restart it, to make sure that + // persistence persists across database object instances. + delete database_; + database_ = ::firebase::database::Database::GetInstance(app_); + + // Offline mode. If persistence works, we should still be able to fetch + // our value even though we're offline. + database_->GoOffline(); + ref = database_->GetReferenceFromUrl(working_url.c_str()); + + { + // Check that we can get the offline value via ValueListener. + ExpectValueListener listener(kPersistenceString); + ref.Child(test_name).AddValueListener(&listener); + ASSERT_TRUE(listener.WaitForExpectedValue()); + ref.Child(test_name).RemoveValueListener(&listener); + } + + // Check that we can get the offline value via GetValue(). + firebase::Future offline_value = + ref.Child(test_name).GetValue(); + WaitForCompletion(offline_value, "GetValue (offline)"); + EXPECT_EQ(offline_value.result()->value(), kPersistenceString); + + // Go back online. + database_->GoOnline(); + SignIn(); + + // Check the online value via GetValue(). + firebase::Future online_value = + ref.Child(test_name).GetValue(); + WaitForCompletion(online_value, "GetValue (online)"); + EXPECT_EQ(online_value.result()->value(), kPersistenceString); + // Clean up manually. + WaitForCompletion(ref.RemoveValue(), "RemoveValue"); +} + +TEST_F(FirebaseDatabaseTest, TestRunTransaction) { + const char* test_name = test_info_->name(); + + SignIn(); + firebase::database::DatabaseReference ref = CreateWorkingPath(); + + // Test running a transaction. This will call RunTransaction and set + // some values, including incrementing the player's score. + firebase::Future transaction_future; + static const int kInitialScore = 500; + // Set an initial score of 500 points. + WaitForCompletion( + ref.Child(test_name).Child("player_score").SetValue(kInitialScore), + "SetInitialScoreValue"); + // The transaction will set the player's item and class, and increment + // their score by 100 points. + int score_delta = 100; + transaction_future = ref.Child(test_name).RunTransaction( + [](firebase::database::MutableData* data, void* score_delta_void) { + LogDebug(" Transaction function executing."); + data->Child("player_item").set_value("Fire sword"); + data->Child("player_class").set_value("Warrior"); + // Increment the current score by 100. + int64_t score = + data->Child("player_score").value().AsInt64().int64_value(); + data->Child("player_score") + .set_value(score + *reinterpret_cast(score_delta_void)); + return firebase::database::kTransactionResultSuccess; + }, + &score_delta); + WaitForCompletion(transaction_future, "RunTransaction"); + + // If the transaction succeeded, let's read back the values that were + // written to confirm they match. + if (transaction_future.error() == firebase::database::kErrorNone) { + firebase::Future read_future = + ref.Child(test_name).GetValue(); + WaitForCompletion(read_future, "ReadTransactionResults"); + + const firebase::database::DataSnapshot& read_result = *read_future.result(); + EXPECT_EQ(read_result.children_count(), 3); + EXPECT_TRUE(read_result.HasChild("player_item")); + EXPECT_EQ(read_result.Child("player_item").value(), "Fire sword"); + EXPECT_TRUE(read_result.HasChild("player_class")); + EXPECT_EQ(read_result.Child("player_class").value(), "Warrior"); + EXPECT_TRUE(read_result.HasChild("player_score")); + EXPECT_EQ(read_result.Child("player_score").value().AsInt64(), + kInitialScore + score_delta); + EXPECT_EQ(read_result.value(), transaction_future.result()->value()); + } +} + +TEST_F(FirebaseDatabaseTest, TestUpdateChildren) { + const char* test_name = test_info_->name(); + + SignIn(); + + firebase::database::DatabaseReference ref = CreateWorkingPath(); + WaitForCompletion( + ref.Child(test_name).SetValue(std::map{ + {"hello", "world"}, {"apples", "oranges"}, {"puppies", "kittens"}}), + "SetValue"); + firebase::Future read_future = + ref.Child(test_name).GetValue(); + WaitForCompletion(read_future, "GetValue 1"); + EXPECT_THAT(read_future.result()->value().map(), + testing::UnorderedElementsAre(Pair("hello", "world"), + Pair("apples", "oranges"), + Pair("puppies", "kittens"))); + firebase::Future update_future = ref.Child(test_name).UpdateChildren( + {{"hello", "galaxy"}, + {"pears", "grapes"}, + {"bunnies", "birbs"}, + {"timestamp", firebase::database::ServerTimestamp()}}); + WaitForCompletion(update_future, "UpdateChildren"); + read_future = ref.Child(test_name).GetValue(); + WaitForCompletion(read_future, "GetValue 2"); + int64_t current_time_milliseconds = + static_cast(time(nullptr)) * 1000L; + EXPECT_THAT( + read_future.result()->value().map(), + UnorderedElementsAre( + Pair("apples", "oranges"), Pair("puppies", "kittens"), + Pair("hello", "galaxy"), Pair("pears", "grapes"), + Pair("bunnies", "birbs"), + Pair("timestamp", TimestampIsNear(current_time_milliseconds)))); +} + +TEST_F(FirebaseDatabaseTest, TestQueryFiltering) { + const char* test_name = test_info_->name(); + + // Set up an initial corpus of data that we can then query filter. + // This test exercises the following methods on Query: + // OrderByChild, OrderByKey, OrderByPriority, OrderByValue + // StartAt, EndAt, EqualTo + // LimitToFirst, LimitToLast + firebase::Variant initial_data = std::map{ + {"apple", 1}, + {"banana", "two"}, + {"durian", + std::map{{"subkey", 3}, + {"value", "delicious"}}}, + {"fig", 3.3}, + {"cranberry", + std::map{{"subkey", 500}, {"value", 9}}}, + {"eggplant", + std::map{{"subkey", 100}, + {"value", "purple"}}}, + {"gooseberry", "honk"}}; + + SignIn(); + + firebase::database::DatabaseReference ref = CreateWorkingPath(); + // On mobile, keep this path synchronized to work around persistence issues. +#if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + ref.SetKeepSynchronized(true); +#endif // defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + WaitForCompletion(ref.Child(test_name).SetValue(initial_data), "SetValue"); + WaitForCompletion(ref.Child(test_name).Child("cranberry").SetPriority(100), + "SetPriority 1"); + WaitForCompletion(ref.Child(test_name).Child("banana").SetPriority(-100), + "SetPriority 2"); + WaitForCompletion(ref.Child(test_name).Child("eggplant").SetPriority(200), + "SetPriority 3"); + WaitForCompletion(ref.Child(test_name).Child("gooseberry").SetPriority(300), + "SetPriority 4"); + { + firebase::Future initial_read = + ref.Child(test_name).GetValue(); + WaitForCompletion(initial_read, "GetValue (initial)"); + EXPECT_EQ(initial_read.result()->value(), initial_data); + EXPECT_THAT( + initial_read.result()->value().map(), + UnorderedElementsAre(Key("apple"), Key("banana"), Key("cranberry"), + Key("durian"), Key("eggplant"), Key("fig"), + Key("gooseberry"))); + } + + // First, try ordering by key. Use LimitToFirst/Last to make sure we get the + // first few or last values (even though std::map will always be sorted by + // key). + { + firebase::Future order_by_key = + ref.Child(test_name).OrderByKey().LimitToFirst(3).GetValue(); + WaitForCompletion(order_by_key, "GetValue (OrderByKey)"); + EXPECT_THAT( + order_by_key.result()->value().map(), + UnorderedElementsAre(Key("apple"), Key("banana"), Key("cranberry"))); + } + { + firebase::Future order_by_child = + ref.Child(test_name).OrderByChild("subkey").LimitToLast(3).GetValue(); + WaitForCompletion(order_by_child, "GetValue (OrderByChild)"); + EXPECT_THAT( + order_by_child.result()->value().map(), + UnorderedElementsAre(Key("cranberry"), Key("durian"), Key("eggplant"))); + } + { + firebase::Future order_by_priority = + ref.Child(test_name).OrderByPriority().LimitToLast(3).GetValue(); + WaitForCompletion(order_by_priority, "GetValue (OrderByPriority)"); + EXPECT_THAT(order_by_priority.result()->value().map(), + UnorderedElementsAre(Key("cranberry"), Key("eggplant"), + Key("gooseberry"))); + } + { + firebase::Future order_by_value = + ref.Child(test_name).OrderByValue().LimitToFirst(3).GetValue(); + WaitForCompletion(order_by_value, "GetValue (OrderByValue)"); + EXPECT_THAT( + order_by_value.result()->value().map(), + UnorderedElementsAre(Key("apple"), Key("fig"), Key("gooseberry"))); + } + + // Try StartAt, EndAt, EqualTo. + { + firebase::Future start_at = + ref.Child(test_name).OrderByKey().StartAt("d").GetValue(); + WaitForCompletion(start_at, "GetValue (StartAt)"); + EXPECT_THAT(start_at.result()->value().map(), + UnorderedElementsAre(Key("durian"), Key("eggplant"), Key("fig"), + Key("gooseberry"))); + } + { + firebase::Future end_at = + ref.Child(test_name).OrderByKey().EndAt("f").GetValue(); + WaitForCompletion(end_at, "GetValue (EndAt)"); + EXPECT_THAT( + end_at.result()->value().map(), + UnorderedElementsAre(Key("apple"), Key("banana"), Key("cranberry"), + Key("durian"), Key("eggplant"))); + } + { + firebase::Future start_and_end_at = + ref.Child(test_name).OrderByKey().StartAt("c").EndAt("f").GetValue(); + WaitForCompletion(start_and_end_at, "GetValue (StartAndEndAt)"); + EXPECT_THAT( + start_and_end_at.result()->value().map(), + UnorderedElementsAre(Key("cranberry"), Key("durian"), Key("eggplant"))); + } + { + firebase::Future equal_to = + ref.Child(test_name).OrderByKey().EqualTo("fig").GetValue(); + WaitForCompletion(equal_to, "GetValue (EqualTo)"); + EXPECT_THAT(equal_to.result()->value().map(), + UnorderedElementsAre(Key("fig"))); + } +} + +// A simple ValueListener that logs the values it sees. +class LoggingValueListener : public firebase::database::ValueListener { + public: + LoggingValueListener() : got_error_(false) {} + void OnValueChanged( + const firebase::database::DataSnapshot& snapshot) override { + LogDebug(" ValueListener.OnValueChanged(%s)", + FirebaseTest::VariantToString(snapshot.value()).c_str()); + last_seen_value_ = snapshot.value(); + seen_values_.push_back(snapshot.value()); + } + void OnCancelled(const firebase::database::Error& error_code, + const char* error_message) override { + LogError("ValueListener got error: %d: %s", error_code, error_message); + got_error_ = true; + } + const firebase::Variant& last_seen_value() { return last_seen_value_; } + bool has_seen_value(const firebase::Variant& value) { + return std::find(seen_values_.begin(), seen_values_.end(), value) != + seen_values_.end(); + } + size_t num_seen_values() { return seen_values_.size(); } + + bool got_error() { return got_error_; } + + private: + firebase::Variant last_seen_value_; + std::vector seen_values_; + bool got_error_; +}; + +TEST_F(FirebaseDatabaseTest, TestValueListener) { + const char* test_name = test_info_->name(); + + SignIn(); + + firebase::database::DatabaseReference ref = CreateWorkingPath(); + WaitForCompletion(ref.Child(test_name).SetValue(0), "SetValue"); + LoggingValueListener* listener = new LoggingValueListener(); + + // Attach the listener, then set 3 values, which will trigger the + // listener. + ref.Child(test_name).AddValueListener(listener); + + // The listener's OnChanged callback is triggered once when the listener is + // attached and again every time the data, including children, changes. + // Wait for here for a moment for the initial values to be received. + ProcessEvents(1000); + + WaitForCompletion(ref.Child(test_name).SetValue(1), "SetValue 1"); + WaitForCompletion(ref.Child(test_name).SetValue(2), "SetValue 2"); + WaitForCompletion(ref.Child(test_name).SetValue(3), "SetValue 3"); + + // Wait a moment for the value listener to be triggered. + ProcessEvents(1000); + + ref.Child(test_name).RemoveValueListener(listener); + // Ensure that the listener is not triggered once removed. + WaitForCompletion(ref.Child(test_name).SetValue(4), "SetValue 4"); + + // Wait a moment to ensure the listener is not triggered. + ProcessEvents(1000); + + EXPECT_FALSE(listener->got_error()); + // Ensure that the listener was only triggered 4 times, with the values + // 0 (the initial value), 1, 2, and 3. The value 4 should not be present. + EXPECT_EQ(listener->num_seen_values(), 4); + EXPECT_TRUE(listener->has_seen_value(0)); + EXPECT_TRUE(listener->has_seen_value(1)); + EXPECT_TRUE(listener->has_seen_value(2)); + EXPECT_TRUE(listener->has_seen_value(3)); + EXPECT_FALSE(listener->has_seen_value(4)); + + delete listener; +} + +// An simple ChildListener class that simply logs the events it sees. +class LoggingChildListener : public firebase::database::ChildListener { + public: + LoggingChildListener() : got_error_(false) {} + + void OnChildAdded(const firebase::database::DataSnapshot& snapshot, + const char* previous_sibling) override { + LogDebug(" ChildListener.OnChildAdded(%s)", snapshot.key()); + events_.push_back(std::string("added ") + snapshot.key()); + } + void OnChildChanged(const firebase::database::DataSnapshot& snapshot, + const char* previous_sibling) override { + LogDebug(" ChildListener.OnChildChanged(%s)", snapshot.key()); + events_.push_back(std::string("changed ") + snapshot.key()); + } + void OnChildMoved(const firebase::database::DataSnapshot& snapshot, + const char* previous_sibling) override { + LogDebug(" ChildListener.OnChildMoved(%s)", snapshot.key()); + events_.push_back(std::string("moved ") + snapshot.key()); + } + void OnChildRemoved( + const firebase::database::DataSnapshot& snapshot) override { + LogDebug(" ChildListener.OnChildRemoved(%s)", snapshot.key()); + events_.push_back(std::string("removed ") + snapshot.key()); + } + void OnCancelled(const firebase::database::Error& error_code, + const char* error_message) override { + LogError("ChildListener got error: %d: %s", error_code, error_message); + got_error_ = true; + } + + const std::vector& events() { return events_; } + + // Get the total number of Child events this listener saw. + size_t total_events() { return events_.size(); } + + // Get the number of times this event was seen. + int num_events(const std::string& event) { + int count = 0; + for (int i = 0; i < events_.size(); i++) { + if (events_[i] == event) { + count++; + } + } + return count; + } + bool got_error() { return got_error_; } + + public: + // Vector of strings defining the events we saw, in order. + std::vector events_; + bool got_error_; +}; + +TEST_F(FirebaseDatabaseTest, TestChildListener) { + const char* test_name = test_info_->name(); + + SignIn(); + + firebase::database::DatabaseReference ref = CreateWorkingPath(); + + LoggingChildListener* listener = new LoggingChildListener(); + ref.Child(test_name) + .OrderByChild("entity_type") + .EqualTo("enemy") + .AddChildListener(listener); + + // The listener's OnChanged callback is triggered once when the listener is + // attached and again every time the data, including children, changes. + // Wait for here for a moment for the initial values to be received. + ProcessEvents(1000); + + EXPECT_FALSE(listener->got_error()); + + // Perform a series of operations that we will then verify with the + // ChildListener's event log. + std::map params; + params["entity_name"] = "cobra"; + params["entity_type"] = "enemy"; + WaitForCompletion( + ref.Child(test_name).Child("0").SetValueAndPriority(params, 0), + "SetEntity0"); // added 0 + params["entity_name"] = "warrior"; + params["entity_type"] = "hero"; + WaitForCompletion( + ref.Child(test_name).Child("1").SetValueAndPriority(params, 10), + "SetEntity1"); // no event + params["entity_name"] = "wizard"; + params["entity_type"] = "hero"; + WaitForCompletion( + ref.Child(test_name).Child("2").SetValueAndPriority(params, 20), + "SetEntity2"); // no event + params["entity_name"] = "rat"; + params["entity_type"] = "enemy"; + WaitForCompletion( + ref.Child(test_name).Child("3").SetValueAndPriority(params, 30), + "SetEntity3"); // added 3 + params["entity_name"] = "thief"; + params["entity_type"] = "enemy"; + WaitForCompletion( + ref.Child(test_name).Child("4").SetValueAndPriority(params, 40), + "SetEntity4"); // added 4 + params["entity_name"] = "paladin"; + params["entity_type"] = "hero"; + WaitForCompletion( + ref.Child(test_name).Child("5").SetValueAndPriority(params, 50), + "SetEntity5"); // no event + params["entity_name"] = "ghost"; + params["entity_type"] = "enemy"; + WaitForCompletion( + ref.Child(test_name).Child("6").SetValueAndPriority(params, 60), + "SetEntity6"); // added 6 + params["entity_name"] = "dragon"; + params["entity_type"] = "enemy"; + WaitForCompletion( + ref.Child(test_name).Child("7").SetValueAndPriority(params, 70), + "SetEntity7"); // added 7 + // Now the thief becomes a hero! + WaitForCompletion( + ref.Child(test_name).Child("4").Child("entity_type").SetValue("hero"), + "SetEntity4Type"); + // Now the dragon becomes a super-dragon! + WaitForCompletion(ref.Child(test_name) + .Child("7") + .Child("entity_name") + .SetValue("super-dragon"), + "SetEntity7Name"); // changed 7 + // Now the super-dragon becomes an mega-dragon! + WaitForCompletion(ref.Child(test_name) + .Child("7") + .Child("entity_name") + .SetValue("mega-dragon"), + "SetEntity7NameAgain"); // changed 7 + // And now we change a hero entity, which the Query ignores. + WaitForCompletion(ref.Child(test_name) + .Child("2") + .Child("entity_name") + .SetValue("super-wizard"), + "SetEntity2Value"); // no event + // Now poof, the mega-dragon is gone. + WaitForCompletion(ref.Child(test_name).Child("7").RemoveValue(), + "RemoveEntity7"); // removed 7 + + // Wait a few seconds for the child listener to be triggered. + ProcessEvents(1000); + // Unregister the listener, so it stops triggering. + ref.Child(test_name) + .OrderByChild("entity_type") + .EqualTo("enemy") + .RemoveChildListener(listener); + // Wait a few seconds for the child listener to finish up. + ProcessEvents(1000); + + // Make one more change, to ensure the listener has been removed. + WaitForCompletion(ref.Child(test_name).Child("6").SetPriority(0), + "SetEntity6Priority"); + // We are expecting to have the following events: + EXPECT_THAT(listener->events(), + ElementsAre("added 0", "added 3", "added 4", "added 6", "added 7", + "removed 4", "changed 7", "changed 7", "removed 7")); + delete listener; +} + +TEST_F(FirebaseDatabaseTest, TestOnDisconnect) { + const char* test_name = test_info_->name(); + + SignIn(); + firebase::database::DatabaseReference ref = CreateWorkingPath(); + std::string saved_url = ref.url(); + + // Set up some ondisconnect handlers to set several values. + WaitForCompletion( + ref.Child(test_name).Child("SetValueTo1").OnDisconnect()->SetValue(1), + "OnDisconnectSetValue1"); + WaitForCompletion(ref.Child(test_name) + .Child("SetValue2Priority3") + .OnDisconnect() + ->SetValueAndPriority(2, 3), + "OnDisconnect (SetValue2Priority3)"); + WaitForCompletion(ref.Child(test_name) + .Child("SetValueButThenCancel") + .OnDisconnect() + ->SetValue("Going to cancel this"), + "OnDisconnect (SetValueToCancel)"); + WaitForCompletion(ref.Child(test_name) + .Child("SetValueButThenCancel") + .OnDisconnect() + ->Cancel(), + "OnDisconnect (Cancel)"); + // Set a value that we will then remove on disconnect. + WaitForCompletion( + ref.Child(test_name).Child("RemoveValue").SetValue("Will be removed"), + "SetValue (RemoveValue)"); + WaitForCompletion( + ref.Child(test_name).Child("RemoveValue").OnDisconnect()->RemoveValue(), + "OnDisconnect (RemoveValue)"); + // Set up a map to pass to OnDisconnect()->UpdateChildren(). + std::map children; + children.insert(std::make_pair("one", 1)); + children.insert(std::make_pair("two", 2)); + children.insert(std::make_pair("three", 3)); + WaitForCompletion(ref.Child(test_name) + .Child("UpdateChildren") + .OnDisconnect() + ->UpdateChildren(children), + "OnDisconnect (UpdateChildren)"); + + // Set up a listener to wait for the ondisconnect action to occur. + ExpectValueListener* listener = new ExpectValueListener(1); + ref.Child(test_name).Child("SetValueTo1").AddValueListener(listener); + LogDebug("Disconnecting..."); + database_->GoOffline(); + + listener->WaitForExpectedValue(); + ref.Child(test_name).Child("SetValueTo1").RemoveValueListener(listener); + + // Let go of the reference we already had. + ref = firebase::database::DatabaseReference(); + + delete listener; + listener = nullptr; + + LogDebug("Reconnecting..."); + database_->GoOnline(); + + /// Check that the DisconnectionHandler actions were performed. + /// Get a brand new reference to the location to be sure. + ref = database_->GetReferenceFromUrl(saved_url.c_str()); + firebase::Future future = + ref.Child(test_name).GetValue(); + WaitForCompletion(future, "GetValue (OnDisconnectChanges)"); + const firebase::database::DataSnapshot& result = *future.result(); + EXPECT_TRUE(result.HasChild("SetValueTo1")); + EXPECT_EQ(result.Child("SetValueTo1").value(), 1); + EXPECT_TRUE(result.HasChild("SetValue2Priority3")); + EXPECT_EQ(result.Child("SetValue2Priority3").value(), 2); + EXPECT_EQ(result.Child("SetValue2Priority3").priority().AsInt64(), 3); + EXPECT_FALSE(result.HasChild("RemoveValue")); + EXPECT_FALSE(result.HasChild("SetValueButThenCancel")); + EXPECT_TRUE(result.HasChild("UpdateChildren")); + EXPECT_THAT( + result.Child("UpdateChildren").value().map(), + UnorderedElementsAre(Pair("one", 1), Pair("two", 2), Pair("three", 3))); +} + +TEST_F(FirebaseDatabaseTest, TestInvalidatingReferencesWhenDeletingDatabase) { + SignIn(); + + // Create a file so we can get its metadata and check that it's properly + // invalidated. + firebase::database::DatabaseReference ref = CreateWorkingPath(); + cleanup_paths_.erase(std::find(cleanup_paths_.begin(), cleanup_paths_.end(), + ref)); // We'll remove this path manually. + firebase::database::Query query = ref.LimitToFirst(10); + firebase::Future set_future = + ref.Child("Invalidating").SetValue(kSimpleString); + WaitForCompletion(set_future, "SetValue"); + firebase::Future get_future = + ref.Child("Invalidating").GetValue(); + WaitForCompletion(get_future, "GetValue"); + firebase::database::DataSnapshot snapshot = *get_future.result(); + firebase::Future delete_future = + ref.Child("Invalidating").RemoveValue(); + WaitForCompletion(delete_future, "RemoveValue"); + + EXPECT_TRUE(ref.is_valid()); + EXPECT_TRUE(query.is_valid()); + EXPECT_TRUE(snapshot.is_valid()); + EXPECT_NE(set_future.status(), firebase::kFutureStatusInvalid); + EXPECT_NE(get_future.status(), firebase::kFutureStatusInvalid); + EXPECT_NE(delete_future.status(), firebase::kFutureStatusInvalid); + + delete database_; + database_ = nullptr; + + EXPECT_FALSE(ref.is_valid()); + EXPECT_FALSE(query.is_valid()); + EXPECT_FALSE(snapshot.is_valid()); + EXPECT_EQ(set_future.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(get_future.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(delete_future.status(), firebase::kFutureStatusInvalid); +} + +TEST_F(FirebaseDatabaseTest, TestInvalidatingReferencesWhenDeletingApp) { + SignIn(); + + // Create a file so we can get its metadata and check that it's properly + // invalidated. + firebase::database::DatabaseReference ref = CreateWorkingPath(true); + firebase::database::Query query = ref.LimitToFirst(10); + firebase::Future set_future = + ref.Child("Invalidating").SetValue(kSimpleString); + WaitForCompletion(set_future, "SetValue"); + firebase::Future get_future = + ref.Child("Invalidating").GetValue(); + WaitForCompletion(get_future, "GetValue"); + firebase::database::DataSnapshot snapshot = *get_future.result(); + firebase::Future delete_future = + ref.Child("Invalidating").SetValue(firebase::Variant::Null()); + WaitForCompletion(delete_future, "DeleteValue"); + + EXPECT_TRUE(ref.is_valid()); + EXPECT_TRUE(query.is_valid()); + EXPECT_TRUE(snapshot.is_valid()); + EXPECT_NE(set_future.status(), firebase::kFutureStatusInvalid); + EXPECT_NE(get_future.status(), firebase::kFutureStatusInvalid); + EXPECT_NE(delete_future.status(), firebase::kFutureStatusInvalid); + + // Deleting App should invalidate all the objects and Futures, same as + // deleting Database. + delete app_; + app_ = nullptr; + + EXPECT_FALSE(ref.is_valid()); + EXPECT_FALSE(query.is_valid()); + EXPECT_FALSE(snapshot.is_valid()); + EXPECT_EQ(set_future.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(get_future.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(delete_future.status(), firebase::kFutureStatusInvalid); +} + +TEST_F(FirebaseDatabaseTest, TestInfoConnected) { + SignIn(); + firebase::database::DatabaseReference ref = CreateWorkingPath(); + // Force getting a value so that we are connected to the database. + WaitForCompletion(ref.GetValue(), "GetValue 1 [ignored]"); + firebase::database::DatabaseReference info = + database_->GetReference(".info").Child("connected"); + { + auto connected = info.GetValue(); + WaitForCompletion(connected, "GetValue 2"); + EXPECT_EQ(connected.result()->value(), true); + } + LogDebug("Disconnecting..."); + database_->GoOffline(); + { + auto disconnected = info.GetValue(); + WaitForCompletion(disconnected, "GetValue 3"); + EXPECT_EQ(disconnected.result()->value(), false); + } + LogDebug("Reconnecting..."); + database_->GoOnline(); + ProcessEvents(1000); + // Force getting a value so that we reconnect to the database. + WaitForCompletion(ref.GetValue(), "GetValue 4 [ignored]"); + { + auto reconnected = info.GetValue(); + WaitForCompletion(reconnected, "GetValue 5"); + EXPECT_EQ(reconnected.result()->value(), true); + } +} + +TEST_F(FirebaseDatabaseTest, TestGetReferenceWillNullArgument) { + EXPECT_FALSE(database_->GetReference(nullptr).is_valid()); +} + +TEST_F(FirebaseDatabaseTest, TestGetReferenceFromUrlWithNullArgument) { + EXPECT_FALSE(database_->GetReferenceFromUrl(nullptr).is_valid()); +} + +TEST_F(FirebaseDatabaseTest, TestDatabaseReferenceChildWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + EXPECT_FALSE(ref.Child(nullptr).is_valid()); +} + +TEST_F(FirebaseDatabaseTest, TestDataSnapshotChildWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + firebase::Future future = ref.GetValue(); + WaitForCompletion(future, "ref.GetValue()"); + const firebase::database::DataSnapshot* snapshot = future.result(); + EXPECT_FALSE(snapshot->Child(nullptr).is_valid()); +} + +TEST_F(FirebaseDatabaseTest, TestDataSnapshotHasChildWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + firebase::Future future = ref.GetValue(); + WaitForCompletion(future, "ref.GetValue()"); + const firebase::database::DataSnapshot* snapshot = future.result(); + EXPECT_FALSE(snapshot->HasChild(nullptr)); +} + +TEST_F(FirebaseDatabaseTest, TestMutableDataChildWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + firebase::Future transaction_future; + transaction_future = ref.RunTransaction( + [](firebase::database::MutableData* data, void*) { + // This is the best way we have to check validity of MutableData as we + // don't currently expose an is_valid method. + EXPECT_EQ(data->Child(nullptr).value(), firebase::Variant::Null()); + return firebase::database::kTransactionResultSuccess; + }, + nullptr); + WaitForCompletion(transaction_future, "RunTransaction"); +} + +TEST_F(FirebaseDatabaseTest, TestMutableDataHasChildWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + firebase::Future transaction_future; + transaction_future = ref.RunTransaction( + [](firebase::database::MutableData* data, void*) { + EXPECT_FALSE(data->HasChild(nullptr)); + return firebase::database::kTransactionResultSuccess; + }, + nullptr); + WaitForCompletion(transaction_future, "RunTransaction"); +} + +TEST_F(FirebaseDatabaseTest, TestQueryOrderByChildWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + EXPECT_FALSE(ref.OrderByChild(nullptr).is_valid()); +} + +TEST_F(FirebaseDatabaseTest, TestQueryStartAtWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + EXPECT_FALSE( + ref.StartAt(firebase::Variant("SomeString"), nullptr).is_valid()); +} + +TEST_F(FirebaseDatabaseTest, TestQueryEndAtWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + EXPECT_FALSE(ref.EndAt(firebase::Variant("SomeString"), nullptr).is_valid()); +} + +TEST_F(FirebaseDatabaseTest, TestQueryEqualToWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + EXPECT_FALSE( + ref.EqualTo(firebase::Variant("SomeString"), nullptr).is_valid()); +} + +TEST_F(FirebaseDatabaseTest, TestValueListenerWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + ref.AddValueListener(nullptr); +} + +TEST_F(FirebaseDatabaseTest, TestChildListenerWithNullArgument) { + firebase::database::DatabaseReference ref = + database_->GetReference("Nothing/will/be/uploaded/here"); + ref.AddChildListener(nullptr); +} + +} // namespace firebase_testapp_automated diff --git a/dynamic_links/integration_test/AndroidManifest.xml b/dynamic_links/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..40b00fd352 --- /dev/null +++ b/dynamic_links/integration_test/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynamic_links/integration_test/CMakeLists.txt b/dynamic_links/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..cb8dd7a869 --- /dev/null +++ b/dynamic_links/integration_test/CMakeLists.txt @@ -0,0 +1,225 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_dynamic_links firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/dynamic_links/integration_test/Firebase_Cpp_Dynamic_Links_Test_App_Dev.mobileprovision b/dynamic_links/integration_test/Firebase_Cpp_Dynamic_Links_Test_App_Dev.mobileprovision new file mode 100644 index 0000000000..541ff35cfa Binary files /dev/null and b/dynamic_links/integration_test/Firebase_Cpp_Dynamic_Links_Test_App_Dev.mobileprovision differ diff --git a/dynamic_links/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/dynamic_links/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/dynamic_links/integration_test/Images.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/dynamic_links/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/dynamic_links/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/dynamic_links/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/dynamic_links/integration_test/Info.plist b/dynamic_links/integration_test/Info.plist new file mode 100644 index 0000000000..5d64337d39 --- /dev/null +++ b/dynamic_links/integration_test/Info.plist @@ -0,0 +1,56 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.FirebaseCppDynamicLinksTestApp.dev + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLSchemes + + com.google.FirebaseCppDynamicLinksTestApp.dev + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.google.FirebaseCppDynamicLinksTestApp.dev + CFBundleURLSchemes + + com.google.FirebaseCppDynamicLinksTestApp.dev + + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + YOUR_REVERSED_CLIENT_ID + firebase-game-loop + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + NSContactsUsageDescription + Invite others to use the app. + + diff --git a/dynamic_links/integration_test/LaunchScreen.storyboard b/dynamic_links/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/dynamic_links/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dynamic_links/integration_test/LibraryManifest.xml b/dynamic_links/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..bfbc445ec9 --- /dev/null +++ b/dynamic_links/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/dynamic_links/integration_test/Podfile b/dynamic_links/integration_test/Podfile new file mode 100644 index 0000000000..cf15082836 --- /dev/null +++ b/dynamic_links/integration_test/Podfile @@ -0,0 +1,15 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase Dynamic Links test application. + +target 'integration_test' do + pod 'Firebase/DynamicLinks', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/dynamic_links/integration_test/build.gradle b/dynamic_links/integration_test/build.gradle new file mode 100644 index 0000000000..f4f6e04b5c --- /dev/null +++ b/dynamic_links/integration_test/build.gradle @@ -0,0 +1,76 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.android.dynamiclinks.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + dynamicLinks +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/dynamic_links/integration_test/googletest.cmake b/dynamic_links/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/dynamic_links/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/dynamic_links/integration_test/gradle/wrapper/gradle-wrapper.jar b/dynamic_links/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/dynamic_links/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/dynamic_links/integration_test/gradle/wrapper/gradle-wrapper.properties b/dynamic_links/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/dynamic_links/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/dynamic_links/integration_test/gradlew b/dynamic_links/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/dynamic_links/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/dynamic_links/integration_test/gradlew.bat b/dynamic_links/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/dynamic_links/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/dynamic_links/integration_test/integration_test.entitlements b/dynamic_links/integration_test/integration_test.entitlements new file mode 100644 index 0000000000..3ccfd33539 --- /dev/null +++ b/dynamic_links/integration_test/integration_test.entitlements @@ -0,0 +1,12 @@ + + + + + application-identifier + $(AppIdentifierPrefix)$(CFBundleIdentifier) + com.apple.developer.associated-domains + + applinks:zx93d.app.goo.gl + + + diff --git a/dynamic_links/integration_test/integration_test.xcodeproj/project.pbxproj b/dynamic_links/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d1d6b852d1 --- /dev/null +++ b/dynamic_links/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/dynamic_links/integration_test/proguard.pro b/dynamic_links/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/dynamic_links/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/dynamic_links/integration_test/res/layout/main.xml b/dynamic_links/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/dynamic_links/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/dynamic_links/integration_test/res/values/strings.xml b/dynamic_links/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..f8cbe3cc1f --- /dev/null +++ b/dynamic_links/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase Dynamic Links Integration Test + diff --git a/dynamic_links/integration_test/settings.gradle b/dynamic_links/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/dynamic_links/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/dynamic_links/integration_test/src/integration_test.cc b/dynamic_links/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..efc13c227a --- /dev/null +++ b/dynamic_links/integration_test/src/integration_test.cc @@ -0,0 +1,698 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/dynamic_links.h" +#include "firebase/dynamic_links/components.h" +#include "firebase/internal/platform.h" +#include "firebase/log.h" +#include "firebase/util.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using app_framework::LogDebug; +using app_framework::LogInfo; + +using app_framework::ProcessEvents; +using firebase_test_framework::FirebaseTest; + +// Bundle IDs needed for opening Dynamic Links. +static const char kIOSBundleID[] = + "com.google.FirebaseCppDynamicLinksTestApp.dev"; +static const char kAndroidBundleID[] = + "com.google.android.dynamiclinks.testapp"; +static const char kIOSAppStoreID[] = "2233445566"; // Placeholder ID. + +static const char kDomainUriPrefixInvalidError[] = + "kDomainUriPrefix is not valid, link shortening will fail.\n" + "To resolve this:\n" + "* Goto the Firebase console https://firebase.google.com/console/\n" + "* Click on the Dynamic Links tab\n" + "* Copy the URI prefix e.g https://a12cd.app.goo.gl or " + " https://your-project.page.link\n" + "* Replace the value of kDomainUriPrefix with the copied URI prefix.\n"; + +// IMPORTANT: You need to set this to a valid URI prefix from the Firebase +// console (see kDomainUriPrefixInvalidError for the details). +static const char* kDomainUriPrefix = "https://REPLACE_WITH_YOUR_URI_PREFIX"; + +#define TARGET_URL_PREFIX "https://mysite.example.com" + +// When one of the tests tries to open a URL, it suppresses the other tests +// that are attempting to do the same, since only one URL can be opened at a +// time. It does so by setting the "current test" flag to its own test name. +static const char kCurrentTestKey[] = "openurl_current_test"; + +class TestListener; + +class FirebaseDynamicLinksTest : public FirebaseTest { + public: + static void SetUpTestSuite(); + static void TearDownTestSuite(); + + protected: + // Try to claim the "current test" flag, returning true if successful and + // false if not. Because tests run in sequence, this does not actually + // require any mutexes. This returns true if it was already claimed by this + // test, or if no test was claiming it before (in which case, now this test + // is). + bool ClaimCurrentTest(const char* test_name); + // Release the "current test" flag, allowing the next test to run. + void ReleaseCurrentTest(); + + static firebase::App* shared_app_; + static TestListener* shared_listener_; + static bool is_desktop_stub_; + // A list of persistent keys we've saved on the device, to be erased on + // shutdown after all tests are finished. + static std::vector cleanup_persistent_keys_; +}; + +firebase::App* FirebaseDynamicLinksTest::shared_app_ = nullptr; +TestListener* FirebaseDynamicLinksTest::shared_listener_ = nullptr; +bool FirebaseDynamicLinksTest::is_desktop_stub_ = false; +// NOLINTNEXTLINE +std::vector FirebaseDynamicLinksTest::cleanup_persistent_keys_; + +// Handles a received dynamic link. +class TestListener : public firebase::dynamic_links::Listener { + public: + TestListener() : received_link_(false) {} + void OnDynamicLinkReceived( + const firebase::dynamic_links::DynamicLink* dynamic_link) override { + LogInfo("Received dynamic link: %s", dynamic_link->url.c_str()); + link_ = *dynamic_link; + received_link_ = true; + } + + bool WaitForDynamicLink(firebase::dynamic_links::DynamicLink* link_output) { + const int kWaitSeconds = 10; + for (int i = 0; i < kWaitSeconds; i++) { + ProcessEvents(1000); + if (received_link_) { + *link_output = link_; + return true; + } + } + return false; + } + bool received_link_; + firebase::dynamic_links::DynamicLink link_; +}; + +void FirebaseDynamicLinksTest::SetUpTestSuite() { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); + + firebase::SetLogLevel(firebase::kLogLevelDebug); + LogDebug("Initialize Firebase App."); + +#if defined(__ANDROID__) + shared_app_ = ::firebase::App::Create(app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + shared_app_ = ::firebase::App::Create(); +#endif // defined(__ANDROID__) + + LogDebug("Initializing Firebase Dynamic Links."); + + shared_listener_ = new TestListener(); + + ::firebase::ModuleInitializer initializer; + initializer.Initialize( + shared_app_, shared_listener_, + [](::firebase::App* app, void* listener_void) { + LogDebug("Try to initialize Firebase Dynamic Links"); + firebase::InitResult result; + result = firebase::dynamic_links::Initialize( + *app, reinterpret_cast( + listener_void)); + return result; + }); + + FirebaseTest::WaitForCompletion(initializer.InitializeLastResult(), + "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + is_desktop_stub_ = false; +#if !defined(__ANDROID__) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + is_desktop_stub_ = true; +#endif // !defined(__ANDROID__) && !(defined(TARGET_OS_IPHONE) && + // TARGET_OS_IPHONE) + + LogDebug("Successfully initialized Firebase Dynamic Links."); +} + +void FirebaseDynamicLinksTest::TearDownTestSuite() { + // On teardown, delete all the persistent keys we should clean up, as long as + // there is no longer a current test running. + std::string value; + if (GetPersistentString(kCurrentTestKey, &value) && !value.empty()) { + // Don't clean up the persistent keys yet, not until all the tests are done. + return; + } + LogDebug("Tests finished, cleaning up all persistent keys."); + for (int i = 0; i < cleanup_persistent_keys_.size(); ++i) { + SetPersistentString(cleanup_persistent_keys_[i].c_str(), nullptr); + } + cleanup_persistent_keys_.clear(); + + LogDebug("Shutdown Firebase Dynamic Links."); + firebase::dynamic_links::Terminate(); + + delete shared_listener_; + shared_listener_ = nullptr; + + LogDebug("Shutdown Firebase App."); + delete shared_app_; + shared_app_ = nullptr; + + ProcessEvents(100); +} + +// Test cases below. + +TEST_F(FirebaseDynamicLinksTest, TestInitializeAndTerminate) { + // Already tested via SetUp() and TearDown(). +} + +TEST_F(FirebaseDynamicLinksTest, CheckForDomainUriPrefix) { + ASSERT_EQ(strstr(kDomainUriPrefix, "REPLACE_WITH"), nullptr) + << kDomainUriPrefixInvalidError; +} + +TEST_F(FirebaseDynamicLinksTest, TestCreateLongLink) { + firebase::dynamic_links::GoogleAnalyticsParameters analytics_parameters; + analytics_parameters.source = "mysource"; + analytics_parameters.medium = "mymedium"; + analytics_parameters.campaign = "mycampaign"; + analytics_parameters.term = "myterm"; + analytics_parameters.content = "mycontent"; + + firebase::dynamic_links::IOSParameters ios_parameters("com.myapp.bundleid"); + ios_parameters.fallback_url = TARGET_URL_PREFIX "/fallback"; + ios_parameters.custom_scheme = "mycustomscheme"; + ios_parameters.minimum_version = "1.2.3"; + ios_parameters.ipad_bundle_id = "com.myapp.bundleid.ipad"; + ios_parameters.ipad_fallback_url = TARGET_URL_PREFIX "/fallbackipad"; + + firebase::dynamic_links::ITunesConnectAnalyticsParameters + app_store_parameters; + app_store_parameters.affiliate_token = "abcdefg"; + app_store_parameters.campaign_token = "hijklmno"; + app_store_parameters.provider_token = "pq-rstuv"; + + firebase::dynamic_links::AndroidParameters android_parameters( + "com.myapp.packageid"); + android_parameters.fallback_url = TARGET_URL_PREFIX "/fallback"; + android_parameters.minimum_version = 12; + + firebase::dynamic_links::SocialMetaTagParameters social_parameters; + social_parameters.title = "My App!"; + social_parameters.description = "My app is awesome!"; + social_parameters.image_url = TARGET_URL_PREFIX "/someimage.jpg"; + + firebase::dynamic_links::DynamicLinkComponents components( + "https://google.com/abc", kDomainUriPrefix); + components.google_analytics_parameters = &analytics_parameters; + components.ios_parameters = &ios_parameters; + components.itunes_connect_analytics_parameters = &app_store_parameters; + components.android_parameters = &android_parameters; + components.social_meta_tag_parameters = &social_parameters; + + firebase::dynamic_links::GeneratedDynamicLink generated_link = + firebase::dynamic_links::GetLongLink(components); + + if (is_desktop_stub_) { + // On desktop, it's enough that we just don't crash. + SUCCEED(); + return; + } + + EXPECT_TRUE(generated_link.error.empty()); + EXPECT_NE(generated_link.url, ""); + EXPECT_EQ(generated_link.url.find(kDomainUriPrefix), 0) + << "Dynamic Link URL (" << generated_link.url + << ") does not begin with Domain URI Prefix (" << kDomainUriPrefix << ")"; + if (!generated_link.warnings.empty()) { + LogDebug("GetLongLink warnings:"); + for (auto it = generated_link.warnings.begin(); + it != generated_link.warnings.end(); ++it) { + LogDebug(" %s", it->c_str()); + } + } +} + +TEST_F(FirebaseDynamicLinksTest, TestGetShortLinkFromComponents) { + firebase::dynamic_links::GoogleAnalyticsParameters analytics_parameters; + analytics_parameters.source = "mysource"; + analytics_parameters.medium = "mymedium"; + analytics_parameters.campaign = "mycampaign"; + analytics_parameters.term = "myterm"; + analytics_parameters.content = "mycontent"; + + firebase::dynamic_links::IOSParameters ios_parameters("com.myapp.bundleid"); + ios_parameters.fallback_url = TARGET_URL_PREFIX "/fallback"; + ios_parameters.custom_scheme = "mycustomscheme"; + ios_parameters.minimum_version = "1.2.3"; + ios_parameters.ipad_bundle_id = "com.myapp.bundleid.ipad"; + ios_parameters.ipad_fallback_url = TARGET_URL_PREFIX "/fallbackipad"; + + firebase::dynamic_links::ITunesConnectAnalyticsParameters + app_store_parameters; + app_store_parameters.affiliate_token = "abcdefg"; + app_store_parameters.campaign_token = "hijklmno"; + app_store_parameters.provider_token = "pq-rstuv"; + + firebase::dynamic_links::AndroidParameters android_parameters( + "com.myapp.packageid"); + android_parameters.fallback_url = TARGET_URL_PREFIX "/fallback"; + android_parameters.minimum_version = 12; + + firebase::dynamic_links::SocialMetaTagParameters social_parameters; + social_parameters.title = "My App!"; + social_parameters.description = "My app is awesome!"; + social_parameters.image_url = TARGET_URL_PREFIX "/someimage.jpg"; + + firebase::dynamic_links::DynamicLinkComponents components( + "https://google.com/def", kDomainUriPrefix); + components.google_analytics_parameters = &analytics_parameters; + components.ios_parameters = &ios_parameters; + components.itunes_connect_analytics_parameters = &app_store_parameters; + components.android_parameters = &android_parameters; + components.social_meta_tag_parameters = &social_parameters; + + firebase::Future future = + firebase::dynamic_links::GetShortLink(components); + WaitForCompletion(future, "GetShortLinkFromComponents"); + + if (is_desktop_stub_) { + // On desktop, it's enough that we just don't crash. + SUCCEED(); + return; + } + + const firebase::dynamic_links::GeneratedDynamicLink& generated_link = + *future.result(); + + EXPECT_TRUE(generated_link.error.empty()); + EXPECT_NE(generated_link.url, ""); + EXPECT_EQ(generated_link.url.find(kDomainUriPrefix), 0) + << "Dynamic Link URL (" << generated_link.url + << ") does not begin with Domain URI Prefix (" << kDomainUriPrefix << ")"; + if (!generated_link.warnings.empty()) { + LogDebug("GetShortLinkFromComponents warnings:"); + for (auto it = generated_link.warnings.begin(); + it != generated_link.warnings.end(); ++it) { + LogDebug(" %s", it->c_str()); + } + } +} + +TEST_F(FirebaseDynamicLinksTest, TestGetShortLinkFromLongLink) { + firebase::dynamic_links::GoogleAnalyticsParameters analytics_parameters; + analytics_parameters.source = "mysource"; + analytics_parameters.medium = "mymedium"; + analytics_parameters.campaign = "mycampaign"; + analytics_parameters.term = "myterm"; + analytics_parameters.content = "mycontent"; + + firebase::dynamic_links::IOSParameters ios_parameters("com.myapp.bundleid"); + ios_parameters.fallback_url = TARGET_URL_PREFIX "/fallback"; + ios_parameters.custom_scheme = "mycustomscheme"; + ios_parameters.minimum_version = "1.2.3"; + ios_parameters.ipad_bundle_id = "com.myapp.bundleid.ipad"; + ios_parameters.ipad_fallback_url = TARGET_URL_PREFIX "/fallbackipad"; + + firebase::dynamic_links::ITunesConnectAnalyticsParameters + app_store_parameters; + app_store_parameters.affiliate_token = "abcdefg"; + app_store_parameters.campaign_token = "hijklmno"; + app_store_parameters.provider_token = "pq-rstuv"; + + firebase::dynamic_links::AndroidParameters android_parameters( + "com.myapp.packageid"); + android_parameters.fallback_url = TARGET_URL_PREFIX "/fallback"; + android_parameters.minimum_version = 12; + + firebase::dynamic_links::SocialMetaTagParameters social_parameters; + social_parameters.title = "My App!"; + social_parameters.description = "My app is awesome!"; + social_parameters.image_url = TARGET_URL_PREFIX "/someimage.jpg"; + + firebase::dynamic_links::DynamicLinkComponents components( + "https://google.com/ghi", kDomainUriPrefix); + components.google_analytics_parameters = &analytics_parameters; + components.ios_parameters = &ios_parameters; + components.itunes_connect_analytics_parameters = &app_store_parameters; + components.android_parameters = &android_parameters; + components.social_meta_tag_parameters = &social_parameters; + + firebase::dynamic_links::GeneratedDynamicLink long_link = + firebase::dynamic_links::GetLongLink(components); + + if (is_desktop_stub_) { + // On desktop, it's enough that we just don't crash. + SUCCEED(); + return; + } + + EXPECT_NE(long_link.url, ""); + + firebase::dynamic_links::DynamicLinkOptions options; + options.path_length = firebase::dynamic_links::kPathLengthShort; + firebase::Future future = + firebase::dynamic_links::GetShortLink(long_link.url.c_str(), options); + WaitForCompletion(future, "GetShortLinkFromLongLink"); + + const firebase::dynamic_links::GeneratedDynamicLink& generated_link = + *future.result(); + + EXPECT_TRUE(generated_link.error.empty()); + EXPECT_NE(generated_link.url, ""); + EXPECT_EQ(generated_link.url.find(kDomainUriPrefix), 0) + << "Dynamic Link URL (" << generated_link.url + << ") does not begin with Domain URI Prefix (" << kDomainUriPrefix << ")"; + if (!generated_link.warnings.empty()) { + LogDebug("GetShortLinkFromLongLink warnings:"); + for (auto it = generated_link.warnings.begin(); + it != generated_link.warnings.end(); ++it) { + LogDebug(" %s", it->c_str()); + } + } +} + +bool FirebaseDynamicLinksTest::ClaimCurrentTest(const char* test_name) { + // Tests using OpenUrlInBrowser must be run one at a time per run of the app. + // The workflow for these tests is: + // + // Run #1: Test A opens its link in browser, tests B & C do nothing. + // Run #2: Test A verifies that its link loaded, test B opens its link in + // browser, test C does nothing. + // Run #3: Test A remembers whether its link had loaded, test B verifies that + // its link loaded, test C opens its link in browser. + // Run #4: Tests A & B remember whether their links had loaded, test C + // verifies that its link loaded. + // + // This is accomplished by setting the value of kCurrentTestKey, which tells + // us which of the tests is currently doing its thing. Each test can also set + // a state variable saying whether they are opening the link in browser (the + // starting state), verifying that the link opened, or previously opened (or + // failed to open) the link. Tests that previously failed to open the link + // will continue to register a FAIL until all the tests are finished. + std::string value; + if (!GetPersistentString(kCurrentTestKey, &value) || value == test_name) { + // If not already set to it, take ownership of the current test. + if (value != test_name) { + SetPersistentString(kCurrentTestKey, test_name); + } + return true; + } + return false; +} + +void FirebaseDynamicLinksTest::ReleaseCurrentTest() { + SetPersistentString(kCurrentTestKey, nullptr); +} + +static firebase::dynamic_links::DynamicLinkComponents GenerateComponentsForTest( + const char* url) { + static firebase::dynamic_links::AndroidParameters android_parameters( + kAndroidBundleID); + static firebase::dynamic_links::IOSParameters ios_parameters(kIOSBundleID); + ios_parameters.app_store_id = kIOSAppStoreID; + static firebase::dynamic_links::SocialMetaTagParameters social_parameters; + static firebase::dynamic_links::ITunesConnectAnalyticsParameters + app_store_parameters; + static firebase::dynamic_links::GoogleAnalyticsParameters + analytics_parameters; + firebase::dynamic_links::DynamicLinkComponents components(url, + kDomainUriPrefix); + components.google_analytics_parameters = &analytics_parameters; + components.ios_parameters = &ios_parameters; + components.itunes_connect_analytics_parameters = &app_store_parameters; + components.android_parameters = &android_parameters; + components.social_meta_tag_parameters = &social_parameters; + return components; +} +static const char kStateSentLink[] = "sentLink"; +static const char kStateReceivedLink[] = "receivedLink"; +static const char kStateReceivedLinkFail[] = "receivedLinkFail"; + +TEST_F(FirebaseDynamicLinksTest, TestOpeningLongLinkInRunningApp) { + // On iOS, the dynamic link landing page requires a click. + // On Android, the first time a dynamic link is clicked on the device, Google + // Play services shows a TOS popup. Either way, this test requires user + // interaction. +#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || defined(__ANDROID__) + TEST_REQUIRES_USER_INTERACTION; +#endif // (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || + // defined(__ANDROID__) + + // This test uses a persistent key to keep track of how it's running. + const char kUrlToOpen[] = "https://google.com/test_opening_long_link"; + std::string persistent_key_str = test_info_->name(); + const char* persistent_key = persistent_key_str.c_str(); + + bool owns_current_test = ClaimCurrentTest(persistent_key); + + cleanup_persistent_keys_.push_back(persistent_key); + std::string value; + if (owns_current_test && !GetPersistentString(persistent_key, &value)) { + // The first time, create a dynamic link and open it in the browser. + LogDebug("First run, creating and opening long dynamic link..."); + + firebase::dynamic_links::DynamicLinkComponents components = + GenerateComponentsForTest(kUrlToOpen); + firebase::dynamic_links::GeneratedDynamicLink link = + firebase::dynamic_links::GetLongLink(components); + + if (is_desktop_stub_) { + // On desktop, it's enough that we just don't crash. + LogDebug("Succeeded as stub."); + SUCCEED(); + return; + } + EXPECT_TRUE(link.error.empty()); + SetPersistentString(persistent_key, kStateSentLink); + // This will trigger the test to start over. + OpenUrlInBrowser(link.url.c_str()); + exit(0); // Kill the app after opening the URL so it can be restarted + // properly. + } else if (owns_current_test && GetPersistentString(persistent_key, &value) && + value == kStateSentLink) { + // The second time, check that we received the dynamic link. + LogDebug("Second run, checking for dynamic link..."); + firebase::dynamic_links::DynamicLink got_link; + EXPECT_TRUE(shared_listener_->WaitForDynamicLink(&got_link)); + EXPECT_EQ(got_link.url, kUrlToOpen); + if (got_link.url == kUrlToOpen) { + SetPersistentString(persistent_key, kStateReceivedLink); + } else { + SetPersistentString(persistent_key, kStateReceivedLinkFail); + } + ReleaseCurrentTest(); + } else if (GetPersistentString(persistent_key, &value) && + value == kStateReceivedLink) { + // Already verified the link was correct. + LogDebug("Previously verified that dynamic link was received."); + SUCCEED(); + } else if (GetPersistentString(persistent_key, &value) && + value == kStateReceivedLinkFail) { + // Already verified the link failed. + FAIL() << "Previous attempt to get link failed."; + } else { + LogDebug("Skipping this test because another test has taken ownership."); + SUCCEED(); + } +} + +TEST_F(FirebaseDynamicLinksTest, TestOpeningShortLinkFromLongLinkInRunningApp) { + // On iOS, the dynamic link landing page requires a click. + // On Android, the first time a dynamic link is clicked on the device, Google + // Play services shows a TOS popup. Either way, this test requires user + // interaction. +#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || defined(__ANDROID__) + TEST_REQUIRES_USER_INTERACTION; +#endif // (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || + // defined(__ANDROID__) + + // This test uses a persistent key to keep track of how it's running. + const char kUrlToOpen[] = + "https://google.com/test_opening_short_link_from_long_link"; + std::string persistent_key_str = test_info_->name(); + const char* persistent_key = persistent_key_str.c_str(); + + bool owns_current_test = ClaimCurrentTest(persistent_key); + + cleanup_persistent_keys_.push_back(persistent_key); + std::string value; + if (owns_current_test && !GetPersistentString(persistent_key, &value)) { + // The first time, create a dynamic link and open it in the browser. + LogDebug( + "First run, creating and opening short dynamic link from long link..."); + firebase::dynamic_links::DynamicLinkComponents components = + GenerateComponentsForTest(kUrlToOpen); + firebase::dynamic_links::GeneratedDynamicLink long_link = + firebase::dynamic_links::GetLongLink(components); + // Shorten link. + firebase::dynamic_links::DynamicLinkOptions options; + options.path_length = firebase::dynamic_links::kPathLengthShort; + firebase::Future future = + firebase::dynamic_links::GetShortLink(long_link.url.c_str(), options); + + if (is_desktop_stub_) { + // On desktop, it's enough that we just don't crash. + LogDebug("Succeeded as stub."); + SUCCEED(); + return; + } + + WaitForCompletion(future, "GetShortLinkFromLongLink"); + const firebase::dynamic_links::GeneratedDynamicLink& link = + *future.result(); + + EXPECT_TRUE(link.error.empty()); + SetPersistentString(persistent_key, kStateSentLink); + // This will trigger the test to start over. + OpenUrlInBrowser(link.url.c_str()); + exit(0); // Kill the app after opening the URL so it can be restarted + // properly; + } else if (owns_current_test && GetPersistentString(persistent_key, &value) && + value == kStateSentLink) { + // The second time, check that we received the dynamic link. + LogDebug("Second run, checking for dynamic link..."); + firebase::dynamic_links::DynamicLink got_link; + EXPECT_TRUE(shared_listener_->WaitForDynamicLink(&got_link)); + EXPECT_EQ(got_link.url, kUrlToOpen); + if (got_link.url == kUrlToOpen) { + SetPersistentString(persistent_key, kStateReceivedLink); + } else { + SetPersistentString(persistent_key, kStateReceivedLinkFail); + } + ReleaseCurrentTest(); + } else if (GetPersistentString(persistent_key, &value) && + value == kStateReceivedLink) { + // Already verified the link was correct. + LogDebug("Previously verified that dynamic link was received."); + SUCCEED(); + } else if (GetPersistentString(persistent_key, &value) && + value == kStateReceivedLinkFail) { + // Already verified the link failed. + FAIL() << "Previous attempt to get link failed."; + } else { + LogDebug("Skipping this test because another test has taken ownership."); + SUCCEED(); + } +} + +TEST_F(FirebaseDynamicLinksTest, + TestOpeningShortLinkFromComponentsInRunningApp) { + // On iOS, the dynamic link landing page requires a click. + // On Android, the first time a dynamic link is clicked on the device, Google + // Play services shows a TOS popup. Either way, this test requires user + // interaction. +#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || defined(__ANDROID__) + TEST_REQUIRES_USER_INTERACTION; +#endif // (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || + // defined(__ANDROID__) + + // This test uses a persistent key to keep track of how it's running. + const char kUrlToOpen[] = + "https://google.com/test_opening_short_link_from_components"; + std::string persistent_key_str = test_info_->name(); + const char* persistent_key = persistent_key_str.c_str(); + + bool owns_current_test = ClaimCurrentTest(persistent_key); + + cleanup_persistent_keys_.push_back(persistent_key); + std::string value; + if (owns_current_test && !GetPersistentString(persistent_key, &value)) { + // The first time, create a dynamic link and open it in the browser. + LogDebug( + "First run, creating and opening short dynamic link from " + "components..."); + firebase::dynamic_links::DynamicLinkComponents components = + GenerateComponentsForTest(kUrlToOpen); + firebase::Future future = + firebase::dynamic_links::GetShortLink(components); + + if (is_desktop_stub_) { + // On desktop, it's enough that we just don't crash. + LogDebug("Succeeded as stub."); + SUCCEED(); + return; + } + + WaitForCompletion(future, "GetShortLinkFromLongLink"); + const firebase::dynamic_links::GeneratedDynamicLink& link = + *future.result(); + + EXPECT_TRUE(link.error.empty()); + SetPersistentString(persistent_key, kStateSentLink); + // This will trigger the test to start over. + OpenUrlInBrowser(link.url.c_str()); + exit(0); // Kill the app after opening the URL so it can be restarted + // properly. + } else if (owns_current_test && GetPersistentString(persistent_key, &value) && + value == kStateSentLink) { + // The second time, check that we received the dynamic link. + LogDebug("Second run, checking for dynamic link..."); + firebase::dynamic_links::DynamicLink got_link; + EXPECT_TRUE(shared_listener_->WaitForDynamicLink(&got_link)); + EXPECT_EQ(got_link.url, kUrlToOpen); + if (got_link.url == kUrlToOpen) { + SetPersistentString(persistent_key, kStateReceivedLink); + } else { + SetPersistentString(persistent_key, kStateReceivedLinkFail); + } + ReleaseCurrentTest(); + } else if (GetPersistentString(persistent_key, &value) && + value == kStateReceivedLink) { + // Already verified the link was correct. + LogDebug("Previously verified that dynamic link was received."); + SUCCEED(); + } else if (GetPersistentString(persistent_key, &value) && + value == kStateReceivedLinkFail) { + // Already verified the link failed. + FAIL() << "Previous attempt to get link failed."; + } else { + LogDebug("Skipping this test because another test has taken ownership."); + SUCCEED(); + } +} +} // namespace firebase_testapp_automated diff --git a/firestore/integration_test/AndroidManifest.xml b/firestore/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..53e48648f8 --- /dev/null +++ b/firestore/integration_test/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/firestore/integration_test/CMakeLists.txt b/firestore/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..e2b2911825 --- /dev/null +++ b/firestore/integration_test/CMakeLists.txt @@ -0,0 +1,225 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + "-framework SystemConfiguration" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_firestore firebase_auth firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/firestore/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/firestore/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/firestore/integration_test/Images.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/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/firestore/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/firestore/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/firestore/integration_test/Info.plist b/firestore/integration_test/Info.plist new file mode 100644 index 0000000000..c44816f632 --- /dev/null +++ b/firestore/integration_test/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.firebase.cpp.firestore.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.80955193299-dpoqtev9j10pffr05pmsikfue7oc2dal + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/firestore/integration_test/LaunchScreen.storyboard b/firestore/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/firestore/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/firestore/integration_test/LibraryManifest.xml b/firestore/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..ca63578077 --- /dev/null +++ b/firestore/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/firestore/integration_test/Podfile b/firestore/integration_test/Podfile new file mode 100644 index 0000000000..5d5d22fba2 --- /dev/null +++ b/firestore/integration_test/Podfile @@ -0,0 +1,16 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase Realtime Firestore test application. + +target 'integration_test' do + pod 'Firebase/Firestore', '6.24.0' + pod 'Firebase/Auth', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/firestore/integration_test/build.gradle b/firestore/integration_test/build.gradle new file mode 100644 index 0000000000..80257d55a4 --- /dev/null +++ b/firestore/integration_test/build.gradle @@ -0,0 +1,78 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.firebase.cpp.firestore.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + multiDexEnabled true + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth + firestore +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/firestore/integration_test/googletest.cmake b/firestore/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/firestore/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/firestore/integration_test/gradle/wrapper/gradle-wrapper.jar b/firestore/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/firestore/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/firestore/integration_test/gradle/wrapper/gradle-wrapper.properties b/firestore/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/firestore/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/firestore/integration_test/gradlew b/firestore/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/firestore/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/firestore/integration_test/gradlew.bat b/firestore/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/firestore/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/firestore/integration_test/integration_test.xcodeproj/project.pbxproj b/firestore/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..baae0f5067 --- /dev/null +++ b/firestore/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,372 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2AD084A34AA54558B7AFA683 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + path = Pods; + sourceTree = ""; + }; + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + 2AD084A34AA54558B7AFA683 /* Pods */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/firestore/integration_test/proguard.pro b/firestore/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/firestore/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/firestore/integration_test/res/layout/main.xml b/firestore/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/firestore/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/firestore/integration_test/res/values/strings.xml b/firestore/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..a91a6c74e9 --- /dev/null +++ b/firestore/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase Firestore Integration Test + diff --git a/firestore/integration_test/settings.gradle b/firestore/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/firestore/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/firestore/integration_test/src/integration_test.cc b/firestore/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..45f590128a --- /dev/null +++ b/firestore/integration_test/src/integration_test.cc @@ -0,0 +1,565 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include +#include +#include + +#include "app_framework.h" // NOLINT +#include "firebase/auth.h" +#include "firebase/firestore.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using app_framework::GetCurrentTimeInMicroseconds; +using app_framework::LogDebug; +using app_framework::ProcessEvents; +using firebase_test_framework::FirebaseTest; +using testing::ElementsAre; +using testing::Pair; +using testing::ResultOf; +using testing::UnorderedElementsAre; + +// Very basic first-level tests for Firestore. More comprehensive integration +// tests are contained in other source files. +class FirebaseFirestoreBasicTest : public FirebaseTest { + public: + FirebaseFirestoreBasicTest(); + ~FirebaseFirestoreBasicTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + // Initialize Firebase App, Firebase Auth, and Firebase Firestore. + void Initialize(); + // Shut down Firebase Firestore, Firebase Auth, and Firebase App. + void Terminate(); + // Sign in an anonymous user. + void SignIn(); + + // Create a custom-named collection to work with for this test. + firebase::firestore::CollectionReference GetTestCollection(); + + // Add the DocumentReference to the cleanup list. At TearDown, all these + // documents will be deleted. + firebase::firestore::DocumentReference Cleanup( + const firebase::firestore::DocumentReference& doc) { + if (find(cleanup_documents_.begin(), cleanup_documents_.end(), doc) == + cleanup_documents_.end()) { + cleanup_documents_.push_back(doc); + } + // Pass through the DocumentReference to simplify test code. + return doc; + } + + firebase::firestore::DocumentReference Doc(const char* suffix = ""); + + bool initialized_ = false; + firebase::auth::Auth* auth_ = nullptr; + firebase::firestore::Firestore* firestore_ = nullptr; + + std::string collection_name_; + std::vector cleanup_documents_; +}; + +FirebaseFirestoreBasicTest::FirebaseFirestoreBasicTest() { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +FirebaseFirestoreBasicTest::~FirebaseFirestoreBasicTest() { + // Must be cleaned up on exit. + assert(app_ == nullptr); + assert(auth_ == nullptr); + assert(firestore_ == nullptr); +} + +void FirebaseFirestoreBasicTest::SetUp() { + FirebaseTest::SetUp(); + Initialize(); +} + +void FirebaseFirestoreBasicTest::TearDown() { + // Delete the shared path, if there is one. + if (initialized_) { + if (!cleanup_documents_.empty()) { + LogDebug("Cleaning up documents."); + std::vector> cleanups; + cleanups.reserve(cleanup_documents_.size()); + for (int i = 0; i < cleanup_documents_.size(); ++i) { + cleanups.push_back(cleanup_documents_[i].Delete()); + } + for (int i = 0; i < cleanups.size(); ++i) { + WaitForCompletion(cleanups[i], "FirebaseDatabaseTest::TearDown"); + } + cleanup_documents_.clear(); + } + Terminate(); + } + FirebaseTest::TearDown(); +} + +void FirebaseFirestoreBasicTest::Initialize() { + if (initialized_) return; + + InitializeApp(); + + LogDebug("Initializing Firebase Auth and Firebase Firestore."); + + // 0th element has a reference to this object, the rest have the initializer + // targets. + void* initialize_targets[] = {&auth_, &firestore_}; + + const firebase::ModuleInitializer::InitializerFn initializers[] = { + [](::firebase::App* app, void* data) { + void** targets = reinterpret_cast(data); + LogDebug("Attempting to initialize Firebase Auth."); + ::firebase::InitResult result; + *reinterpret_cast<::firebase::auth::Auth**>(targets[0]) = + ::firebase::auth::Auth::GetAuth(app, &result); + return result; + }, + [](::firebase::App* app, void* data) { + void** targets = reinterpret_cast(data); + LogDebug("Attempting to initialize Firebase Firestore."); + ::firebase::InitResult result; + firebase::firestore::Firestore* firestore = + firebase::firestore::Firestore::GetInstance(app, &result); + *reinterpret_cast<::firebase::firestore::Firestore**>(targets[1]) = + firestore; + return result; + }}; + + ::firebase::ModuleInitializer initializer; + initializer.Initialize(app_, initialize_targets, initializers, + sizeof(initializers) / sizeof(initializers[0])); + + WaitForCompletion(initializer.InitializeLastResult(), "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase Auth and Firebase Firestore."); + + initialized_ = true; +} + +void FirebaseFirestoreBasicTest::Terminate() { + if (!initialized_) return; + + if (firestore_) { + LogDebug("Shutdown the Firestore library."); + delete firestore_; + firestore_ = nullptr; + } + if (auth_) { + LogDebug("Shutdown the Auth library."); + delete auth_; + auth_ = nullptr; + } + + TerminateApp(); + + initialized_ = false; + + ProcessEvents(100); +} + +void FirebaseFirestoreBasicTest::SignIn() { + LogDebug("Signing in."); + firebase::Future sign_in_future = + auth_->SignInAnonymously(); + WaitForCompletion(sign_in_future, "SignInAnonymously"); + if (sign_in_future.error() != 0) { + FAIL() << "Ensure your application has the Anonymous sign-in provider " + "enabled in Firebase Console."; + } + ProcessEvents(100); +} + +firebase::firestore::CollectionReference +FirebaseFirestoreBasicTest::GetTestCollection() { + if (collection_name_.empty()) { + // Generate a collection for the test data based on the time in + // milliseconds. + int64_t time_in_microseconds = GetCurrentTimeInMicroseconds(); + + char buffer[21] = {0}; + snprintf(buffer, sizeof(buffer), "test%lld", + static_cast(time_in_microseconds)); // NOLINT + collection_name_ = buffer; + } + return firestore_->Collection(collection_name_.c_str()); +} + +firebase::firestore::DocumentReference FirebaseFirestoreBasicTest::Doc( + const char* suffix) { + std::string path = + std::string( + ::testing::UnitTest::GetInstance()->current_test_info()->name()) + + suffix; + return Cleanup(GetTestCollection().Document(path)); +} + +// Test cases below. + +TEST_F(FirebaseFirestoreBasicTest, TestInitializeAndTerminate) { + // Already tested via SetUp() and TearDown(). +} + +TEST_F(FirebaseFirestoreBasicTest, TestSignIn) { + SignIn(); + EXPECT_NE(auth_->current_user(), nullptr); +} + +TEST_F(FirebaseFirestoreBasicTest, TestAppAndSettings) { + EXPECT_EQ(firestore_->app(), app_); + firebase::firestore::Settings settings = firestore_->settings(); + firestore_->set_settings(settings); + // No comparison operator in settings, so just assume it worked if we didn't + // crash. +} + +TEST_F(FirebaseFirestoreBasicTest, TestNonWrappedTypes) { + const firebase::Timestamp timestamp{1, 2}; + EXPECT_EQ(timestamp.seconds(), 1); + EXPECT_EQ(timestamp.nanoseconds(), 2); + const firebase::firestore::SnapshotMetadata metadata{ + /*has_pending_writes*/ false, /*is_from_cache*/ true}; + EXPECT_FALSE(metadata.has_pending_writes()); + EXPECT_TRUE(metadata.is_from_cache()); + const firebase::firestore::GeoPoint point{1.23, 4.56}; + EXPECT_EQ(point.latitude(), 1.23); + EXPECT_EQ(point.longitude(), 4.56); +} + +TEST_F(FirebaseFirestoreBasicTest, TestCollection) { + firebase::firestore::CollectionReference collection = + firestore_->Collection("foo"); + EXPECT_EQ(collection.firestore(), firestore_); + EXPECT_EQ(collection.id(), "foo"); + EXPECT_EQ(collection.Document("bar").path(), "foo/bar"); +} + +TEST_F(FirebaseFirestoreBasicTest, TestDocument) { + firebase::firestore::DocumentReference document = + firestore_->Document("foo/bar"); + EXPECT_EQ(document.firestore(), firestore_); + EXPECT_EQ(document.path(), "foo/bar"); +} + +TEST_F(FirebaseFirestoreBasicTest, TestSetGet) { + SignIn(); + + firebase::firestore::DocumentReference document = Doc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}, + {"int", firebase::firestore::FieldValue::Integer(123)}}), + "document.Set"); + firebase::Future future = + document.Get(); + WaitForCompletion(future, "document.Get"); + EXPECT_NE(future.result(), nullptr); + EXPECT_THAT(future.result()->GetData(), + UnorderedElementsAre( + Pair("str", firebase::firestore::FieldValue::String("foo")), + Pair("int", firebase::firestore::FieldValue::Integer(123)))); +} + +TEST_F(FirebaseFirestoreBasicTest, TestSetUpdateGet) { + SignIn(); + + firebase::firestore::DocumentReference document = Doc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}, + {"int", firebase::firestore::FieldValue::Integer(123)}}), + "document.Set"); + WaitForCompletion( + document.Update(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(321)}}), + "document.Update"); + firebase::Future future = + document.Get(); + WaitForCompletion(future, "document.Get"); + EXPECT_NE(future.result(), nullptr); + EXPECT_THAT(future.result()->GetData(), + UnorderedElementsAre( + Pair("str", firebase::firestore::FieldValue::String("foo")), + Pair("int", firebase::firestore::FieldValue::Integer(321)))); +} + +TEST_F(FirebaseFirestoreBasicTest, TestSetDelete) { + SignIn(); + + firebase::firestore::DocumentReference document = Doc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("bar")}, + {"int", firebase::firestore::FieldValue::Integer(456)}}), + "document.Set"); + + WaitForCompletion(document.Delete(), "document.Delete"); + firebase::Future future = + document.Get(); + WaitForCompletion(future, "document.Get"); + EXPECT_NE(future.result(), nullptr); + EXPECT_FALSE(future.result()->exists()); + + // TODO: Test error cases (deleting invalid path, etc.) +} + +TEST_F(FirebaseFirestoreBasicTest, TestDocumentListener) { + SKIP_TEST_IF_USING_STLPORT; // STLPort uses EventListener rather than + // std::function. +#if !defined(STLPORT) + SignIn(); + + firebase::firestore::DocumentReference document = Doc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("start")}}), + "document.Set 0"); + + std::vector document_snapshots; + firebase::firestore::ListenerRegistration registration = + document.AddSnapshotListener( + [&](const firebase::firestore::DocumentSnapshot& result, + firebase::firestore::Error error) { + EXPECT_EQ(error, firebase::firestore::kErrorOk); + document_snapshots.push_back(result.GetData()); + }); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("update")}}), + "document.Set 1"); + registration.Remove(); + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("final")}}), + "document.Set 2"); + EXPECT_THAT( + document_snapshots, + ElementsAre( + firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("start")}}, + firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("update")}})); +#endif // !defined(STLPORT) +} + +TEST_F(FirebaseFirestoreBasicTest, TestBatchWrite) { + SignIn(); + + firebase::firestore::DocumentReference document1 = Doc("1"); + firebase::firestore::DocumentReference document2 = Doc("2"); + + firebase::firestore::WriteBatch batch = firestore_->batch(); + batch.Set(document1, + firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("first")}}); + batch.Set(document2, + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(2222)}}); + WaitForCompletion(batch.Commit(), "batch.Commit"); + + // Confirm the updated docs are correct. + auto future1 = Doc("1").Get(); + WaitForCompletion(future1, "document.Get 1"); + EXPECT_THAT(future1.result()->GetData(), + ElementsAre(Pair( + "str", firebase::firestore::FieldValue::String("first")))); + + auto future2 = Doc("2").Get(); + WaitForCompletion(future2, "document.Get 2"); + EXPECT_THAT( + future2.result()->GetData(), + ElementsAre(Pair("int", firebase::firestore::FieldValue::Integer(2222)))); +} + +TEST_F(FirebaseFirestoreBasicTest, TestRunTransaction) { + SignIn(); + + WaitForCompletion( + Doc("1").Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}}), + "document.Set 1"); + WaitForCompletion( + Doc("2").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(123)}}), + "document.Set 2"); + WaitForCompletion( + Doc("3").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(678)}}), + "document.Set 3"); + // Make sure there's no doc 4. + WaitForCompletion(Doc("4").Delete(), "document.Delete 4"); + + auto collection = GetTestCollection(); + + auto transaction_future = firestore_->RunTransaction( + [collection, this](firebase::firestore::Transaction& transaction, + std::string&) -> firebase::firestore::Error { + // Set a default error to ensure that the error is filled in by Get(). + firebase::firestore::Error geterr = + static_cast(-1); + std::string getmsg = "[[uninitialized message]]"; + int64_t prev_int = transaction.Get(Doc("2"), &geterr, &getmsg) + .Get("int") + .integer_value(); + EXPECT_EQ(geterr, firebase::firestore::kErrorOk) << getmsg; + + // Update 1, increment 2, delete 3, add 4. + transaction.Update( + Doc("1"), + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(456)}}); + LogDebug("Previous value: %lld", prev_int); + transaction.Update(Doc("2"), + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer( + prev_int + 100)}}); + transaction.Delete(Doc("3")); + transaction.Set( + Doc("4"), + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(789)}}); + return firebase::firestore::kErrorOk; + }); + + WaitForCompletion(transaction_future, "firestore.RunTransaction"); + + (void)Doc("4"); // Add new doc to cleanup list + + // Confirm the updated docs are correct. + // First doc had an additional field added. + auto future1 = Doc("1").Get(); + WaitForCompletion(future1, "document.Get 1"); + EXPECT_THAT(future1.result()->GetData(), + UnorderedElementsAre( + Pair("str", firebase::firestore::FieldValue::String("foo")), + Pair("int", firebase::firestore::FieldValue::Integer(456)))); + + // Second doc was incremented by 100. + auto future2 = Doc("2").Get(); + WaitForCompletion(future2, "document.Get 2"); + EXPECT_THAT( + future2.result()->GetData(), + ElementsAre(Pair("int", firebase::firestore::FieldValue::Integer(223)))); + + // Third doc was deleted. + auto future3 = Doc("3").Get(); + WaitForCompletion(future3, "document.Get 3"); + EXPECT_FALSE(future3.result()->exists()); + + // Fourth doc was newly added. + auto future4 = Doc("4").Get(); + WaitForCompletion(future4, "document.Get 4"); + EXPECT_THAT( + future4.result()->GetData(), + ElementsAre(Pair("int", firebase::firestore::FieldValue::Integer(789)))); +} + +// TODO: Add test for failing transaction. + +TEST_F(FirebaseFirestoreBasicTest, TestQuery) { + SignIn(); + + firebase::firestore::CollectionReference collection = GetTestCollection(); + // { "int" : 99, "int" : 100, "int" : 101, "int": 102, "str": "hello" } + // Query for int > 100 should return only the 101 and 102 entries. + WaitForCompletion(Doc("1").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(99)}}), + "document.Set 1"); + WaitForCompletion( + Doc("2").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(100)}}), + "document.Set 2"); + WaitForCompletion( + Doc("3").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(101)}}), + "document.Set 3"); + WaitForCompletion( + Doc("4").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(102)}}), + "document.Set 4"); + WaitForCompletion( + Doc("5").Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("hello")}}), + "document.Set 5"); + + firebase::firestore::Query query = collection.WhereGreaterThan( + "int", firebase::firestore::FieldValue::Integer(100)); + auto query_future = query.Get(); + WaitForCompletion(query_future, "query.Get"); + EXPECT_NE(query_future.result(), nullptr); + auto DocumentSnapshot_GetData = + [](const firebase::firestore::DocumentSnapshot& ds) { + return ds.GetData(); + }; + EXPECT_THAT( + query_future.result()->documents(), + UnorderedElementsAre( + ResultOf(DocumentSnapshot_GetData, + ElementsAre(Pair( + "int", firebase::firestore::FieldValue::Integer(102)))), + ResultOf( + DocumentSnapshot_GetData, + ElementsAre(Pair( + "int", firebase::firestore::FieldValue::Integer(101)))))); +} + +TEST_F(FirebaseFirestoreBasicTest, + TestInvalidatingReferencesWhenDeletingFirestore) { + delete firestore_; + firestore_ = nullptr; + // TODO: Ensure existing Firestore objects are invalidated. +} + +TEST_F(FirebaseFirestoreBasicTest, TestInvalidatingReferencesWhenDeletingApp) { + delete app_; + app_ = nullptr; + // TODO: Ensure existing Firestore objects are invalidated. +} + +// TODO: Add test for Auth signout while connected. + +// TODO: Add additional comprehensive tests as needed. + +} // namespace firebase_testapp_automated diff --git a/functions/integration_test/AndroidManifest.xml b/functions/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..329c1e684b --- /dev/null +++ b/functions/integration_test/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/functions/integration_test/CMakeLists.txt b/functions/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..8a536b2095 --- /dev/null +++ b/functions/integration_test/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_functions firebase_auth firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/functions/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/functions/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/functions/integration_test/Images.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/functions/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/functions/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/functions/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/functions/integration_test/Info.plist b/functions/integration_test/Info.plist new file mode 100644 index 0000000000..651edd416c --- /dev/null +++ b/functions/integration_test/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.firebase.cpp.functions.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.760619882947-d7uc3pt0rijrudd8mjhc397q5cbm7969 + firebase-game-loop + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/functions/integration_test/LaunchScreen.storyboard b/functions/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/functions/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/functions/integration_test/LibraryManifest.xml b/functions/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..2c020252c0 --- /dev/null +++ b/functions/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/functions/integration_test/Podfile b/functions/integration_test/Podfile new file mode 100644 index 0000000000..bb0c08bab9 --- /dev/null +++ b/functions/integration_test/Podfile @@ -0,0 +1,16 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Cloud Functions for Firebase test application. + +target 'integration_test' do + pod 'Firebase/Functions', '6.24.0' + pod 'Firebase/Auth', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/functions/integration_test/build.gradle b/functions/integration_test/build.gradle new file mode 100644 index 0000000000..97770d76ab --- /dev/null +++ b/functions/integration_test/build.gradle @@ -0,0 +1,77 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.firebase.cpp.functions.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth + functions +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/functions/integration_test/googletest.cmake b/functions/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/functions/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/functions/integration_test/gradle/wrapper/gradle-wrapper.jar b/functions/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/functions/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/functions/integration_test/gradle/wrapper/gradle-wrapper.properties b/functions/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/functions/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/functions/integration_test/gradlew b/functions/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/functions/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/functions/integration_test/gradlew.bat b/functions/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/functions/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/functions/integration_test/integration_test.xcodeproj/project.pbxproj b/functions/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d1d6b852d1 --- /dev/null +++ b/functions/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/functions/integration_test/proguard.pro b/functions/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/functions/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/functions/integration_test/res/layout/main.xml b/functions/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/functions/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/functions/integration_test/res/values/strings.xml b/functions/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..b1d23c931a --- /dev/null +++ b/functions/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Cloud Functions for Firebase Integration Test + diff --git a/functions/integration_test/settings.gradle b/functions/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/functions/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/functions/integration_test/src/integration_test.cc b/functions/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..4b58e2c4bf --- /dev/null +++ b/functions/integration_test/src/integration_test.cc @@ -0,0 +1,326 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/auth.h" +#include "firebase/functions.h" +#include "firebase/util.h" +#include "firebase/variant.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using app_framework::LogDebug; + +using app_framework::ProcessEvents; +using firebase_test_framework::FirebaseTest; + +const char kIntegrationTestRootPath[] = "integration_test_data"; + +class FirebaseFunctionsTest : public FirebaseTest { + public: + FirebaseFunctionsTest(); + ~FirebaseFunctionsTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + // Initialize Firebase App, Firebase Auth, and Firebase Functions. + void Initialize(); + // Shut down Firebase Functions, Firebase Auth, and Firebase App. + void Terminate(); + // Sign in an anonymous user. + void SignIn(); + + firebase::Future TestFunction( + const char* function_name, const firebase::Variant* const function_data, + const firebase::Variant& expected_result, + firebase::functions::Error expected_error = + firebase::functions::kErrorNone); + + bool initialized_; + firebase::auth::Auth* auth_; + firebase::functions::Functions* functions_; +}; + +FirebaseFunctionsTest::FirebaseFunctionsTest() + : initialized_(false), auth_(nullptr), functions_(nullptr) { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +FirebaseFunctionsTest::~FirebaseFunctionsTest() { + // Must be cleaned up on exit. + assert(app_ == nullptr); + assert(auth_ == nullptr); + assert(functions_ == nullptr); +} + +void FirebaseFunctionsTest::SetUp() { + FirebaseTest::SetUp(); + Initialize(); +} + +void FirebaseFunctionsTest::TearDown() { + // Delete the shared path, if there is one. + if (initialized_) { + Terminate(); + } + FirebaseTest::TearDown(); +} + +void FirebaseFunctionsTest::Initialize() { + if (initialized_) return; + + InitializeApp(); + + LogDebug("Initializing Firebase Auth and Firebase Functions."); + + // 0th element has a reference to this object, the rest have the initializer + // targets. + void* initialize_targets[] = {&auth_, &functions_}; + + const firebase::ModuleInitializer::InitializerFn initializers[] = { + [](::firebase::App* app, void* data) { + void** targets = reinterpret_cast(data); + LogDebug("Attempting to initialize Firebase Auth."); + ::firebase::InitResult result; + *reinterpret_cast<::firebase::auth::Auth**>(targets[0]) = + ::firebase::auth::Auth::GetAuth(app, &result); + return result; + }, + [](::firebase::App* app, void* data) { + void** targets = reinterpret_cast(data); + LogDebug("Attempting to initialize Firebase Functions."); + ::firebase::InitResult result; + firebase::functions::Functions* functions = + firebase::functions::Functions::GetInstance(app, &result); + *reinterpret_cast<::firebase::functions::Functions**>(targets[1]) = + functions; + return result; + }}; + + ::firebase::ModuleInitializer initializer; + initializer.Initialize(app_, initialize_targets, initializers, + sizeof(initializers) / sizeof(initializers[0])); + + WaitForCompletion(initializer.InitializeLastResult(), "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase Auth and Firebase Functions."); + + initialized_ = true; +} + +void FirebaseFunctionsTest::Terminate() { + if (!initialized_) return; + + if (functions_) { + LogDebug("Shutdown the Functions library."); + delete functions_; + functions_ = nullptr; + } + if (auth_) { + LogDebug("Shutdown the Auth library."); + delete auth_; + auth_ = nullptr; + } + + TerminateApp(); + + initialized_ = false; + + ProcessEvents(100); +} + +void FirebaseFunctionsTest::SignIn() { + LogDebug("Signing in."); + firebase::Future sign_in_future = + auth_->SignInAnonymously(); + WaitForCompletion(sign_in_future, "SignInAnonymously"); + if (sign_in_future.error() != 0) { + FAIL() << "Ensure your application has the Anonymous sign-in provider " + "enabled in Firebase Console."; + } + ProcessEvents(100); +} + +// A helper function for calling a Firebase Function and waiting on the result. +firebase::Future +FirebaseFunctionsTest::TestFunction( + const char* function_name, const firebase::Variant* const function_data, + const firebase::Variant& expected_result, + firebase::functions::Error expected_error) { + // Create a callable that we can run our test with. + LogDebug("Calling %s", function_name); + firebase::functions::HttpsCallableReference ref; + ref = functions_->GetHttpsCallable(function_name); + + firebase::Future future; + if (function_data == nullptr) { + future = ref.Call(); + } else { + future = ref.Call(*function_data); + } + WaitForCompletion(future, + (std::string("CallFunction ") + function_name).c_str(), + expected_error); + if (!expected_result.is_null()) { + EXPECT_EQ(VariantToString(expected_result), + VariantToString(future.result()->data())) + << "Unexpected result from calling " << function_name; + } + return future; +} + +TEST_F(FirebaseFunctionsTest, TestInitializeAndTerminate) { + // Already tested via SetUp() and TearDown(). +} + +TEST_F(FirebaseFunctionsTest, TestSignIn) { + SignIn(); + EXPECT_NE(auth_->current_user(), nullptr); +} + +TEST_F(FirebaseFunctionsTest, TestFunction) { + SignIn(); + + // addNumbers(5, 7) = 12 + firebase::Variant data(firebase::Variant::EmptyMap()); + data.map()["firstNumber"] = 5; + data.map()["secondNumber"] = 7; + firebase::Variant result = + TestFunction("addNumbers", &data, firebase::Variant::Null()) + .result() + ->data(); + EXPECT_TRUE(result.is_map()); + EXPECT_EQ(result.map()["operationResult"], 12); +} + +TEST_F(FirebaseFunctionsTest, TestFunctionWithData) { + SignIn(); + + std::map data_map; + data_map["bool"] = firebase::Variant::True(); + data_map["int"] = firebase::Variant(2); + data_map["long"] = firebase::Variant(static_cast(3)); + // TODO: Add this back when we add Date support. + // 1998-09-04T22:14:15.926Z + // data["date"] = firebase::functions::EncodeDate(904947255926); + data_map["string"] = firebase::Variant("four"); + std::vector array; + array.push_back(5); + array.push_back(6); + data_map["array"] = firebase::Variant(array); + data_map["null"] = firebase::Variant::Null(); + + std::map expected; + expected["message"] = firebase::Variant("stub response"); + expected["code"] = firebase::Variant(42); + expected["long"] = firebase::Variant(420); + std::vector expected_array; + expected_array.push_back(1); + expected_array.push_back(2); + expected_array.push_back(3); + expected["array"] = firebase::Variant(expected_array); + // TODO: Add this back when we add Date support. + // 2017-09-04T22:14:15.000 + // expected["date"] = + // firebase::Variant(static_cast(1504563255000)); + firebase::Variant data(data_map); + TestFunction("dataTest", &data, firebase::Variant(expected)); +} + +TEST_F(FirebaseFunctionsTest, TestFunctionWithScalar) { + SignIn(); + + // Passing in and returning a scalar value instead of an object. + firebase::Variant data(17); + TestFunction("scalarTest", &data, firebase::Variant(76)); +} + +TEST_F(FirebaseFunctionsTest, TestFunctionWithAuthToken) { + SignIn(); + + // With an auth token. + firebase::Variant data(firebase::Variant::EmptyMap()); + TestFunction("tokenTest", &data, firebase::Variant::EmptyMap()); +} + +// Disabling test temporarily. (b/143598197) +TEST_F(FirebaseFunctionsTest, DISABLED_TestFunctionWithInstanceId) { +#if defined(__ANDROID__) || TARGET_OS_IPHONE + SignIn(); + + // With an instance ID. + firebase::Variant data(firebase::Variant::EmptyMap()); + TestFunction("instanceIdTest", &data, firebase::Variant::EmptyMap()); +#endif // defined(__ANDROID__) || TARGET_OS_IPHONE +} + +TEST_F(FirebaseFunctionsTest, TestFunctionWithNull) { + SignIn(); + + // With an explicit null. + firebase::Variant data(firebase::Variant::Null()); + TestFunction("nullTest", &data, firebase::Variant::Null()); + + // With a void call. + TestFunction("nullTest", nullptr, data); +} + +TEST_F(FirebaseFunctionsTest, TestErrorHandling) { + SignIn(); + + // With the data/result field missing in the response. + TestFunction("missingResultTest", nullptr, firebase::Variant::Null(), + firebase::functions::kErrorInternal); + + // With a response that is not valid JSON. + TestFunction("unhandledErrorTest", nullptr, firebase::Variant::Null(), + firebase::functions::kErrorInternal); + + // With an invalid error code. + TestFunction("unknownErrorTest", nullptr, firebase::Variant::Null(), + firebase::functions::kErrorInternal); + + // With an explicit error code and message. + TestFunction("explicitErrorTest", nullptr, firebase::Variant::Null(), + firebase::functions::kErrorOutOfRange); +} + +} // namespace firebase_testapp_automated diff --git a/instance_id/integration_test/AndroidManifest.xml b/instance_id/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..f62f84f38d --- /dev/null +++ b/instance_id/integration_test/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/instance_id/integration_test/CMakeLists.txt b/instance_id/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..4ceedc6403 --- /dev/null +++ b/instance_id/integration_test/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + +# Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_instance_id firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/instance_id/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/instance_id/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/instance_id/integration_test/Images.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/instance_id/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/instance_id/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/instance_id/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/instance_id/integration_test/Info.plist b/instance_id/integration_test/Info.plist new file mode 100644 index 0000000000..7d05423d5d --- /dev/null +++ b/instance_id/integration_test/Info.plist @@ -0,0 +1,42 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.firebase.instanceid.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.564468233704-670b1qgtr09mp1g8k9uklkjmchln0950 + firebase-game-loop + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/instance_id/integration_test/LaunchScreen.storyboard b/instance_id/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/instance_id/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/instance_id/integration_test/LibraryManifest.xml b/instance_id/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..b61eeb2057 --- /dev/null +++ b/instance_id/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/instance_id/integration_test/Podfile b/instance_id/integration_test/Podfile new file mode 100644 index 0000000000..df257230f2 --- /dev/null +++ b/instance_id/integration_test/Podfile @@ -0,0 +1,16 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase Instance ID test application. + +target 'integration_test' do + pod 'Firebase/Analytics', '6.24.0' + pod 'FirebaseInstanceID', '4.3.4' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/instance_id/integration_test/build.gradle b/instance_id/integration_test/build.gradle new file mode 100644 index 0000000000..4fe87588fe --- /dev/null +++ b/instance_id/integration_test/build.gradle @@ -0,0 +1,76 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.firebase.instanceid.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + instanceId +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/instance_id/integration_test/googletest.cmake b/instance_id/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/instance_id/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/instance_id/integration_test/gradle/wrapper/gradle-wrapper.jar b/instance_id/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/instance_id/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/instance_id/integration_test/gradle/wrapper/gradle-wrapper.properties b/instance_id/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/instance_id/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/instance_id/integration_test/gradlew b/instance_id/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/instance_id/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/instance_id/integration_test/gradlew.bat b/instance_id/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/instance_id/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/instance_id/integration_test/integration_test.xcodeproj/project.pbxproj b/instance_id/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d1d6b852d1 --- /dev/null +++ b/instance_id/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/instance_id/integration_test/proguard.pro b/instance_id/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/instance_id/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/instance_id/integration_test/res/layout/main.xml b/instance_id/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/instance_id/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/instance_id/integration_test/res/values/strings.xml b/instance_id/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..0202809a5f --- /dev/null +++ b/instance_id/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase Instance ID Integration Test + diff --git a/instance_id/integration_test/settings.gradle b/instance_id/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/instance_id/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/instance_id/integration_test/src/integration_test.cc b/instance_id/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..034fceaf55 --- /dev/null +++ b/instance_id/integration_test/src/integration_test.cc @@ -0,0 +1,224 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/instance_id.h" +#include "firebase/util.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using app_framework::LogDebug; + +using app_framework::ProcessEvents; +using firebase_test_framework::FirebaseTest; + +class FirebaseInstanceIdTest : public FirebaseTest { + public: + FirebaseInstanceIdTest(); + ~FirebaseInstanceIdTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + // Initialize Firebase App and Firebase IID. + void Initialize(); + // Shut down Firebase IID and Firebase App. + void Terminate(); + + bool initialized_; + firebase::instance_id::InstanceId* instance_id_; +}; + +FirebaseInstanceIdTest::FirebaseInstanceIdTest() + : initialized_(false), instance_id_(nullptr) { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +FirebaseInstanceIdTest::~FirebaseInstanceIdTest() { + // Must be cleaned up on exit. + assert(app_ == nullptr); + assert(instance_id_ == nullptr); +} + +void FirebaseInstanceIdTest::SetUp() { + FirebaseTest::SetUp(); + Initialize(); +} + +void FirebaseInstanceIdTest::TearDown() { + // Delete the shared path, if there is one. + if (initialized_) { + Terminate(); + } + FirebaseTest::TearDown(); +} + +void FirebaseInstanceIdTest::Initialize() { + if (initialized_) return; + + InitializeApp(); + + LogDebug("Initializing Firebase Instance ID."); + + ::firebase::ModuleInitializer initializer; + initializer.Initialize( + app_, &instance_id_, [](::firebase::App* app, void* target) { + LogDebug("Try to initialize Firebase Instance ID"); + firebase::InitResult result; + firebase::instance_id::InstanceId** iid_ptr = + reinterpret_cast(target); + *iid_ptr = + firebase::instance_id::InstanceId::GetInstanceId(app, &result); + return result; + }); + + FirebaseTest::WaitForCompletion(initializer.InitializeLastResult(), + "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase Instance ID."); + + initialized_ = true; +} + +void FirebaseInstanceIdTest::Terminate() { + if (!initialized_) return; + + if (instance_id_) { + LogDebug("Shutdown the Instance ID library."); + delete instance_id_; + instance_id_ = nullptr; + } + + TerminateApp(); + + initialized_ = false; + + ProcessEvents(100); +} + +TEST_F(FirebaseInstanceIdTest, TestInitializeAndTerminate) { + // Already tested via SetUp() and TearDown(). +} + +TEST_F(FirebaseInstanceIdTest, TestCanGetId) { + firebase::Future id = instance_id_->GetId(); + WaitForCompletion(id, "GetId"); + EXPECT_NE(*id.result(), ""); +} + +TEST_F(FirebaseInstanceIdTest, TestGettingIdTwiceMatches) { + firebase::Future id = instance_id_->GetId(); + WaitForCompletion(id, "GetId"); + EXPECT_NE(*id.result(), ""); + std::string first_id = *id.result(); + id = instance_id_->GetId(); + WaitForCompletion(id, "GetId 2"); + EXPECT_EQ(*id.result(), first_id); +} + +TEST_F(FirebaseInstanceIdTest, TestDeleteIdGivesNewIdNextTime) { + firebase::Future id = instance_id_->GetId(); + WaitForCompletion(id, "GetId"); + EXPECT_NE(*id.result(), ""); + std::string first_id = *id.result(); + + firebase::Future del = instance_id_->DeleteId(); + WaitForCompletion(del, "DeleteId"); + + // Ensure that we now get a different IID. + id = instance_id_->GetId(); + WaitForCompletion(id, "GetId 2"); + EXPECT_NE(*id.result(), ""); +#if defined(__ANDROID__) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + // Desktop is a stub and returns the same ID, but on mobile it should + // return a new ID. + EXPECT_NE(*id.result(), first_id); +#endif // defined(__ANDROID__) || (defined(TARGET_OS_IPHONE) && + // TARGET_OS_IPHONE) +} + +TEST_F(FirebaseInstanceIdTest, TestCanGetToken) { + firebase::Future token = instance_id_->GetToken(); + WaitForCompletion(token, "GetToken"); + EXPECT_NE(*token.result(), ""); +} + +TEST_F(FirebaseInstanceIdTest, TestGettingTokenTwiceMatches) { + firebase::Future token = instance_id_->GetToken(); + WaitForCompletion(token, "GetToken"); + EXPECT_NE(*token.result(), ""); + std::string first_token = *token.result(); + token = instance_id_->GetToken(); + WaitForCompletion(token, "GetToken 2"); + EXPECT_EQ(*token.result(), first_token); +} + +// Test disabled due to flakiness (b/143697451). +TEST_F(FirebaseInstanceIdTest, DISABLED_TestDeleteTokenGivesNewTokenNextTime) { + firebase::Future token = instance_id_->GetToken(); + WaitForCompletion(token, "GetToken"); + EXPECT_NE(*token.result(), ""); + std::string first_token = *token.result(); + + firebase::Future del = instance_id_->DeleteToken(); + WaitForCompletion(del, "DeleteToken"); + + // Ensure that we now get a different IID. + token = instance_id_->GetToken(); + WaitForCompletion(token, "GetToken 2"); + EXPECT_NE(*token.result(), ""); +#if defined(__ANDROID__) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + // Desktop is a stub and returns the same token, but on mobile it should + // return a new token. + EXPECT_NE(*token.result(), first_token); +#endif // defined(__ANDROID__) || (defined(TARGET_OS_IPHONE) && + // TARGET_OS_IPHONE) +} + +TEST_F(FirebaseInstanceIdTest, TestCanGetIdAndTokenTogether) { + firebase::Future id = instance_id_->GetId(); + firebase::Future token = instance_id_->GetToken(); + WaitForCompletion(token, "GetToken"); + WaitForCompletion(id, "GetId"); + EXPECT_NE(*id.result(), ""); + EXPECT_NE(*token.result(), ""); +} + +} // namespace firebase_testapp_automated diff --git a/messaging/integration_test/AndroidManifest.xml b/messaging/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..08ec978150 --- /dev/null +++ b/messaging/integration_test/AndroidManifest.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/messaging/integration_test/CMakeLists.txt b/messaging/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..ad991ccb54 --- /dev/null +++ b/messaging/integration_test/CMakeLists.txt @@ -0,0 +1,226 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + src/android/android_firebase_test_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_messaging firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/messaging/integration_test/Firebase_Cpp_Messaging_Test_App_Dev.mobileprovision b/messaging/integration_test/Firebase_Cpp_Messaging_Test_App_Dev.mobileprovision new file mode 100644 index 0000000000..d2e2a24c51 Binary files /dev/null and b/messaging/integration_test/Firebase_Cpp_Messaging_Test_App_Dev.mobileprovision differ diff --git a/messaging/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/messaging/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/messaging/integration_test/Images.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/messaging/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/messaging/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/messaging/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/messaging/integration_test/Info.plist b/messaging/integration_test/Info.plist new file mode 100644 index 0000000000..319f56a445 --- /dev/null +++ b/messaging/integration_test/Info.plist @@ -0,0 +1,44 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.FirebaseCppMessagingTestApp.dev + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + UIBackgroundModes + + remote-notification + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + firebase-game-loop + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/messaging/integration_test/LaunchScreen.storyboard b/messaging/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/messaging/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/messaging/integration_test/LibraryManifest.xml b/messaging/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..90c5fb93d7 --- /dev/null +++ b/messaging/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/messaging/integration_test/Podfile b/messaging/integration_test/Podfile new file mode 100644 index 0000000000..569d4e37bd --- /dev/null +++ b/messaging/integration_test/Podfile @@ -0,0 +1,15 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase Cloud Messaging test application. + +target 'integration_test' do + pod 'Firebase/Messaging', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/messaging/integration_test/build.gradle b/messaging/integration_test/build.gradle new file mode 100644 index 0000000000..d9b17a960e --- /dev/null +++ b/messaging/integration_test/build.gradle @@ -0,0 +1,76 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.firebase.cpp.messaging.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + messaging +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/messaging/integration_test/googletest.cmake b/messaging/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/messaging/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/messaging/integration_test/gradle/wrapper/gradle-wrapper.jar b/messaging/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/messaging/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/messaging/integration_test/gradle/wrapper/gradle-wrapper.properties b/messaging/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/messaging/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/messaging/integration_test/gradlew b/messaging/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/messaging/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/messaging/integration_test/gradlew.bat b/messaging/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/messaging/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/messaging/integration_test/integration_test.entitlements b/messaging/integration_test/integration_test.entitlements new file mode 100644 index 0000000000..e38c46b835 --- /dev/null +++ b/messaging/integration_test/integration_test.entitlements @@ -0,0 +1,10 @@ + + + + + application-identifier + $(AppIdentifierPrefix)$(CFBundleIdentifier) + aps-environment + development + + diff --git a/messaging/integration_test/integration_test.xcodeproj/project.pbxproj b/messaging/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d1d6b852d1 --- /dev/null +++ b/messaging/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/messaging/integration_test/proguard.pro b/messaging/integration_test/proguard.pro new file mode 100644 index 0000000000..16b4cf1f7a --- /dev/null +++ b/messaging/integration_test/proguard.pro @@ -0,0 +1,3 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } +-keep,includedescriptorclasses public class com.google.firebase.example.SampleNativeActivity { * ; } diff --git a/messaging/integration_test/readme.md b/messaging/integration_test/readme.md new file mode 100644 index 0000000000..ea47af7387 --- /dev/null +++ b/messaging/integration_test/readme.md @@ -0,0 +1,258 @@ +Firebase Cloud Messaging Quickstart +=================================== + +The Firebase Cloud Messaging Test Application (testapp) demonstrates receiving +Firebase Cloud Messages using the Firebase Cloud Messaging C++ SDK. This +application has no user interface and simply logs actions it's performing to the +console. + +Introduction +------------ + +- [Read more about Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/) + +Building and Running the testapp +-------------------------------- +### iOS + - Link your iOS app to the Firebase libraries. + - Get CocoaPods version 1 or later by running, + + ``` + sudo gem install cocoapods --pre + ``` + - From the testapp directory, install the CocoaPods listed in the Podfile by + running, + ``` + pod install + ``` + - Open the generated Xcode workspace (which now has the CocoaPods), + + ``` + open testapp.xcworkspace + ``` + - For further details please refer to the + [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). + + - Register your iOS app with Firebase. + - Create a new app on + [firebase.google.com/console](https://firebase.google.com/console/) + , and attach your iOS app to it. + - For Messaging, you will need an App Store ID. Use something random such + as 12345678." + - You can use "com.google.FirebaseCppMessagingTestApp.dev" as the iOS Bundle ID + while you're testing. + - Add the GoogleService-Info.plist that you downloaded from Firebase + console to the testapp root directory. This file identifies your iOS app + to the Firebase backend. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Add the following frameworks from the Firebase C++ SDK to the project: + - frameworks/ios/universal/firebase.framework + - frameworks/ios/universal/firebase_messaging.framework + - You will need to either, + 1. Check "Copy items if needed" when adding the frameworks, or + 2. Add the framework path in "Framework Search Paths" + - e.g. If you downloaded the Firebase C++ SDK to + `/Users/me/firebase_cpp_sdk`, + then you would add the path + `/Users/me/firebase_cpp_sdk/frameworks/ios/universal`. + - To add the path, in XCode, select your project in the project + navigator, then select your target in the main window. + Select the "Build Settings" tab, and click "All" to see all + the build settings. Scroll down to "Search Paths", and add + your path to "Framework Search Paths". + - You need a valid + [APNs](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html) + certificate. If you don't already have one, see + [Provisioning APNs SSL Certificates.](https://firebase.google.com/docs/cloud-messaging/ios/certs) + - Configure the Xcode project for push messaging. + - Select the `testapp` project from the `Navigator area`. + - Select the `testapp` target from the `Editor area`. + - Select the `General` tab from the `Editor area`. + - Scroll down to `Linked Frameworks and Libraries` and click the `+` + button to add a framework. + - In the window that appears, scroll to `UserNotifications.framework` + and click on that entry, then click on `Add`. This framework will only + appear in Xcode version 8 and higher, required by this library. + - Select the `Capabilities` tab from the `Editor area`. + - Switch `Push Notifications` to `On`. + - Scroll down to `Background Modes` and switch it to `On`. + - Tick the `Remote notifications` box under `Background Modes`. + - In XCode, build & run the sample on an iOS device or simulator. + - The testapp has no user interface. The output of the app can be viewed + via the console. In Xcode, select + `View --> Debug Area --> Activate Console` from the menu. + +### Android +**Register your Android app with Firebase.** + +- Create a new app on +[firebase.google.com/console](https://firebase.google.com/console/) +, and attach your Android app to it. +- You can use "com.google.android.messaging.testapp" as the Package Name +while you're testing. +- To [generate a SHA1](https://developers.google.com/android/guides/client-auth) +run this command (_Note: the default password is 'android'_): + * Mac and Linux: + + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore + ``` + * Windows: + + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore + ``` +- If keytool reports that you do not have a debug.keystore, you can +[create one](http://developer.android.com/tools/publishing/app-signing.html#signing-manually) +with: + + ``` + keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" + ``` +- Add the `google-services.json` file that you downloaded from Firebase + console to the root directory of testapp. This file identifies your + Android app to the Firebase backend. +- For further details please refer to the +[general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). + +- Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + +**Configure your SDK paths** + +- Configure the location of the Firebase C++ SDK by setting the +`firebase_cpp_sdk.dir` Gradle property to the SDK install directory. + * Run this command in the project directory, and modify the path to match your + local installation path: + + ``` + echo "systemProp.firebase_cpp_sdk.dir=/User/$USER/firebase_cpp_sdk" >> gradle.properties + ``` +- Ensure the Android SDK and NDK locations are set in Android Studio. + * From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. + * Android Studio will write these paths to `local.properties`. + +**Optional: Configure your deep link URL** + +- To enable your app to receive deep links via FCM, you will need to add an + intent filter to your AndroidManifest.xml in the native activity that + associates your domain with your app. + +``` + + + + + + + +``` + +**Build & Run** + +- Open `build.gradle` in Android Studio. + - From the Android Studio launch menu, "Open an existing Android Studio + project", and select `build.gradle`. +- Install the SDK Platforms that Android Studio reports missing. +- Build the testapp and run it on an Android device or emulator. +- See [below](#using_the_test_app) for usage instructions. + +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the root directory of the + testapp. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the root directory of the testapp. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the testapp with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the testapp directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the testapp, by either opening the generated project file based on + the platform, or running, + ``` + cmake --build . + ``` + - Execute the testapp by running, + ``` + ./desktop_testapp + ``` + Note that the executable might be under another directory, such as Debug. + - The testapp has no user interface, but the output can be viewed via the + console. Note that Messaging uses a stubbed implementation on desktop, + so functionality is not expected. + +Using the Test App +------------------ + +- Install and run the test app on your iOS or Android device or emulator. +- The application has minimal user interface. The output of the app can be +viewed via the console: + * **iOS**: Open select "View --> Debug Area --> Activate Console" from the + menu in Xcode. + * **Android**: View the logcat output in Android studio or by running + "adb logcat" from the command line. +- When you first run the app, it will print: +`Received Registration Token: `. Copy this code to a text editor. +- Copy the ServerKey from the firebase console: + - Open your project in the + [Firebase Console](https://firebase.google.com/console/). + - Click the gear icon then `Project settings` in the menu on the left + - Select the `Cloud Messaging` tab. + - Copy the `Server Key` +- Replace `` and `` in this command and run it +from the command line. +``` +curl --header "Authorization: key=" --header "Content-Type: application/json" https://android.googleapis.com/gcm/send -d '{"notification":{"title":"Hi","body":"Hello from the Cloud"},"data":{"score":"lots"},"to":""}' +``` +- Observe the command received in the app, via the console output. + +Support +------- + +[https://firebase.google.com/support/](https://firebase.google.com/support/) + +License +------- + +Copyright 2016 Google, Inc. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you 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. diff --git a/messaging/integration_test/res/layout/main.xml b/messaging/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/messaging/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/messaging/integration_test/res/values/strings.xml b/messaging/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..0b0c595859 --- /dev/null +++ b/messaging/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase Cloud Messaging Integration Test + diff --git a/messaging/integration_test/settings.gradle b/messaging/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/messaging/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/messaging/integration_test/src/android/java/com/google/firebase/example/SampleNativeActivity.java b/messaging/integration_test/src/android/java/com/google/firebase/example/SampleNativeActivity.java new file mode 100644 index 0000000000..3706c6acad --- /dev/null +++ b/messaging/integration_test/src/android/java/com/google/firebase/example/SampleNativeActivity.java @@ -0,0 +1,73 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +package com.google.firebase.example; + +import android.app.NativeActivity; +import android.content.Intent; +import android.os.Bundle; +import com.google.firebase.messaging.MessageForwardingService; + +/** + * SampleNativeActivity is a NativeActivity that updates its intent when new intents are sent to + * it. + * + * This is a workaround for a known issue that prevents the native C++ library from responding to + * data payloads when both a data and notification payload are sent to the app while it is in the + * background. + */ +public class SampleNativeActivity extends NativeActivity { + // The key in the intent's extras that maps to the incoming message's message ID. Only sent by + // the server, GmsCore sends EXTRA_MESSAGE_ID_KEY below. Server can't send that as it would get + // stripped by the client. + private static final String EXTRA_MESSAGE_ID_KEY_SERVER = "message_id"; + + // An alternate key value in the intent's extras that also maps to the incoming message's message + // ID. Used by upstream, and set by GmsCore. + private static final String EXTRA_MESSAGE_ID_KEY = "google.message_id"; + + // The key in the intent's extras that maps to the incoming message's sender value. + private static final String EXTRA_FROM = "google.message_id"; + + /** + * Workaround for when a message is sent containing both a Data and Notification payload. + * + * When the app is in the foreground all data payloads are sent to the method + * `::firebase::messaging::Listener::OnMessage`. However, when the app is in the background, if a + * message with both a data and notification payload is receieved the data payload is stored on + * the notification Intent. NativeActivity does not provide native callbacks for onNewIntent, so + * it cannot route the data payload that is stored in the Intent to the C++ function OnMessage. As + * a workaround, we override onNewIntent so that it forwards the intent to the C++ library's + * service which in turn forwards the data to the native C++ messaging library. + */ + @Override + protected void onNewIntent(Intent intent) { + // If we do not have a 'from' field this intent was not a message and should not be handled. It + // probably means this intent was fired by tapping on the app icon. + Bundle extras = intent.getExtras(); + String from = extras.getString(EXTRA_FROM); + String messageId = extras.getString(EXTRA_MESSAGE_ID_KEY); + if (messageId == null) { + messageId = extras.getString(EXTRA_MESSAGE_ID_KEY_SERVER); + } + if (from != null && messageId != null) { + Intent message = new Intent(this, MessageForwardingService.class); + message.setAction(MessageForwardingService.ACTION_REMOTE_INTENT); + message.putExtras(intent); + message.setData(intent.getData()); + startService(message); + } + setIntent(intent); + } +} diff --git a/messaging/integration_test/src/integration_test.cc b/messaging/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..34845e477b --- /dev/null +++ b/messaging/integration_test/src/integration_test.cc @@ -0,0 +1,594 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/messaging.h" +#include "firebase/util.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +// Your Firebase project's Server Key for Cloud Messaging goes here. +// You can get this from Firebase Console, in your Project settings under Cloud +// Messaging. +const char kFcmServerKey[] = + "REPLACE_WITH_YOUR_SERVER_KEY"; + +const char kRestEndpoint[] = "https://fcm.googleapis.com/fcm/send"; + +const char kNotificationLinkKey[] = "gcm.n.link"; +const char kTestLink[] = "https://this-is-a-test-link/"; + +// Give each operation approximately 60 seconds before failing. +const int kTimeoutSeconds = 60; +const char kTestingNotificationKey[] = "fcm_testing_notification"; + +using app_framework::LogDebug; +using app_framework::LogInfo; + +using app_framework::GetCurrentTimeInMicroseconds; +using app_framework::PathForResource; +using app_framework::ProcessEvents; +using firebase_test_framework::FirebaseTest; + +class FirebaseMessagingTest : public FirebaseTest { + public: + FirebaseMessagingTest(); + ~FirebaseMessagingTest() override; + + static void SetUpTestSuite(); + static void TearDownTestSuite(); + + void SetUp() override; + void TearDown() override; + + // Create a request and heads for a test message (returning false if unable to + // do so). send_to can be a FCM token or a topic subscription. + bool CreateTestMessage( + const char* send_to, const char* notification_title, + const char* notification_body, + const std::map& message_fields, + std::string* request_out, + std::map* headers_out); + // Send a message previously created by CreateTestMessage. + void SendTestMessage(const std::string& request, + const std::map& headers); + // Convenience method combining the above. + void SendTestMessage( + const char* send_to, const char* notification_title, + const char* notification_body, + const std::map& message_fields); + + protected: + // Get a unique message ID so we can confirm the correct message is being + // received. + std::string GetUniqueMessageId(); + + // Wait to receive a token. Returns true if a token was received, and places + // it in token_. + static bool WaitForToken(int timeout = kTimeoutSeconds); + // Request messaging permissions from the user. Returns true if permission was + // granted. + bool RequestPermission(); + // Wait to receive a message. Returns true if a message was received and + // returns it in the message_out parameter. + bool WaitForMessage(firebase::messaging::Message* message_out, + int timeout = kTimeoutSeconds); + + static firebase::App* shared_app_; + static firebase::messaging::PollableListener* shared_listener_; + static std::string* shared_token_; + static bool is_desktop_stub_; +}; + +const char kObtainedPermissionKey[] = "messaging_got_permission"; + +std::string* FirebaseMessagingTest::shared_token_ = nullptr; +firebase::App* FirebaseMessagingTest::shared_app_ = nullptr; +firebase::messaging::PollableListener* FirebaseMessagingTest::shared_listener_ = + nullptr; +bool FirebaseMessagingTest::is_desktop_stub_; + +void FirebaseMessagingTest::SetUpTestSuite() { + LogDebug("Initialize Firebase App."); + +#if defined(__ANDROID__) + shared_app_ = ::firebase::App::Create(app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + shared_app_ = ::firebase::App::Create(); +#endif // defined(__ANDROID__) + + LogDebug("Initializing Firebase Cloud Messaging."); + shared_token_ = new std::string(); + + ::firebase::ModuleInitializer initializer; + initializer.Initialize( + shared_app_, &shared_listener_, [](::firebase::App* app, void* userdata) { + LogDebug("Try to initialize Firebase Messaging"); + firebase::messaging::PollableListener** listener = + reinterpret_cast(userdata); + *listener = new firebase::messaging::PollableListener(); + firebase::messaging::MessagingOptions options; + // Prevent the app from requesting permission to show notifications + // immediately upon starting up for the first time. Since it the prompt + // is being suppressed, we must manually display it with a call to + // RequestPermission() elsewhere. + // + // After the first time we've done this, we no longer will suppress the + // permission prompt just for ease of initialization. + std::string value; + if (!GetPersistentString(kObtainedPermissionKey, &value) || + value.empty()) { + // The first time, suppress the notification prompt so that + // RequestPermission will be called. + options.suppress_notification_permission_prompt = true; + } + + return ::firebase::messaging::Initialize(*app, *listener, options); + }); + + WaitForCompletion(initializer.InitializeLastResult(), "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase Cloud Messaging."); + is_desktop_stub_ = false; +#if !defined(ANDROID) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + is_desktop_stub_ = true; +#endif // !defined(ANDROID) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +} + +void FirebaseMessagingTest::TearDownTestSuite() { + LogDebug("All tests finished, cleaning up."); + firebase::messaging::SetListener(nullptr); + delete shared_listener_; + shared_listener_ = nullptr; + delete shared_token_; + shared_token_ = nullptr; + + LogDebug("Shutdown Firebase Cloud Messaging."); + firebase::messaging::Terminate(); + LogDebug("Shutdown Firebase App."); + delete shared_app_; + shared_app_ = nullptr; +} + +FirebaseMessagingTest::FirebaseMessagingTest() { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +FirebaseMessagingTest::~FirebaseMessagingTest() {} + +void FirebaseMessagingTest::SetUp() { FirebaseTest::SetUp(); } + +void FirebaseMessagingTest::TearDown() { FirebaseTest::TearDown(); } + +std::string FirebaseMessagingTest::GetUniqueMessageId() { + int64_t time_in_microseconds = GetCurrentTimeInMicroseconds(); + char buffer[21] = {0}; + snprintf(buffer, sizeof(buffer), "%lld", + static_cast(time_in_microseconds)); // NOLINT + return std::string(buffer); +} + +// send_to can be a FCM token or a topic subscription. +bool FirebaseMessagingTest::CreateTestMessage( + const char* send_to, const char* notification_title, + const char* notification_body, + const std::map& message_fields, + std::string* request_out, std::map* headers_out) { + if (is_desktop_stub_) { + // Don't send HTTP requests in stub mode. + return false; + } + std::map headers; + headers.insert(std::make_pair("Content-type", "application/json")); + headers.insert( + std::make_pair("Authorization", std::string("key=") + kFcmServerKey)); + std::string request; // Build a JSON request. + request += "{\"notification\":{\"title\":\""; + request += notification_title ? notification_title : ""; + request += "\",\"body\":\""; + request += notification_body ? notification_body : ""; + request += "\"},\"data\":{"; + for (auto i = message_fields.begin(); i != message_fields.end(); ++i) { + if (i != message_fields.begin()) request += ","; + request += "\""; + request += i->first; + request += "\":\""; + request += i->second; + request += "\""; + } + request += "}, \"to\":\""; + request += send_to; + // Messages will expire after 5 minutes, so if there are stale/leftover + // messages from a previous run, just wait 5 minutes and try again. + request += "\", \"time_to_live\":300}"; + *request_out = request; + *headers_out = headers; + return true; +} + +void FirebaseMessagingTest::SendTestMessage( + const char* send_to, const char* notification_title, + const char* notification_body, + const std::map& message_fields) { + std::string request; + std::map headers; + EXPECT_TRUE(CreateTestMessage(send_to, notification_title, notification_body, + message_fields, &request, &headers)); + SendTestMessage(request, headers); +} + +void FirebaseMessagingTest::SendTestMessage( + const std::string& request, + const std::map& headers) { + LogDebug("Request: %s", request.c_str()); + LogDebug("Triggering FCM message from server..."); + EXPECT_TRUE( + SendHttpPostRequest(kRestEndpoint, headers, request, nullptr, nullptr)); +} + +bool FirebaseMessagingTest::WaitForToken(int timeout) { + if (!shared_token_->empty()) return true; + + if (is_desktop_stub_) { + // On desktop, just set a stub token. + *shared_token_ = "FcmDesktopStubToken"; + return true; + } + + std::string new_token; + // No new or old token immediately, so wait for a new token. + int seconds = 0; + while (seconds <= timeout) { + if (shared_listener_->PollRegistrationToken(&new_token)) { + if (!new_token.empty()) { + *shared_token_ = new_token; + LogInfo("Got token: %s", shared_token_->c_str()); + return true; + } + } + seconds++; + ProcessEvents(1000); + } + // Failed to get a token. + *shared_token_ = ""; + return false; +} + +bool FirebaseMessagingTest::WaitForMessage( + firebase::messaging::Message* message_out, int timeout) { + int seconds = 0; + while (seconds <= timeout) { + if (shared_listener_->PollMessage(message_out)) { + LogDebug("Received a message."); + return true; + } + seconds++; + ProcessEvents(1000); + } + LogDebug("Did not receive a message."); + *message_out = firebase::messaging::Message(); + return false; +} + +bool FirebaseMessagingTest::RequestPermission() { + std::string value; + if (GetPersistentString(kObtainedPermissionKey, &value) && value == "1") { + return true; // Already got permission. + } + + bool b = WaitForCompletion(firebase::messaging::RequestPermission(), + "RequestPermission"); + if (b) { +#if TARGET_OS_IPHONE + // We only need to pause for permission on iOS. + LogDebug("Pausing so user can grant permission (if needed)."); + ProcessEvents(10000); +#endif // TARGET_OS_IPHONE + SetPersistentString(kObtainedPermissionKey, "1"); + } + return b; +} + +// Test cases below. + +TEST_F(FirebaseMessagingTest, TestRequestPermission) { + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + + // This test may request a permission from the user; if so, the user must + // respond affirmatively. + EXPECT_TRUE(RequestPermission()); +} + +TEST_F(FirebaseMessagingTest, TestReceiveToken) { + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + + EXPECT_TRUE(RequestPermission()); + EXPECT_TRUE(WaitForToken()); + EXPECT_NE(*shared_token_, ""); +} + +TEST_F(FirebaseMessagingTest, TestSubscribeAndUnsubscribe) { + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + + EXPECT_TRUE(RequestPermission()); + EXPECT_TRUE(WaitForToken()); + EXPECT_TRUE(WaitForCompletion(firebase::messaging::Subscribe("SubscribeTest"), + "Subscribe")); + EXPECT_TRUE(WaitForCompletion( + firebase::messaging::Unsubscribe("SubscribeTest"), "Unsubscribe")); +} + +static std::string ConstructHtmlToSendMessage( + const std::string& request, + const std::map& headers, int delay_seconds) { + // Generate some simple HTML/Javascript to pause a few seconds, then send the + // POST request via XMLHttpRequest. + std::string h; + h += ""; + return h; +} + +TEST_F(FirebaseMessagingTest, TestNotification) { + TEST_REQUIRES_USER_INTERACTION; + SKIP_TEST_ON_DESKTOP; + + EXPECT_TRUE(RequestPermission()); + EXPECT_TRUE(WaitForToken()); + + // To test notifications, this test app must be running in the background. To + // accomplish this, switch over to the device's web browser, loading an HTML + // page that will, after a short delay, send the FCM message request to the + // app in the background. This will produce the system notification that you + // can then click on to go back into the app and continue the test. + + std::string unique_id = GetUniqueMessageId(); + std::string token = *shared_token_; + const char kNotificationTitle[] = "FCM Integration Test"; + const char kNotificationBody[] = "Test notification, open to resume testing."; + std::string value; + if (!GetPersistentString(kTestingNotificationKey, &value) || value.empty()) { + // If the notification test is already in progress, just go straight to the + // waiting part. This can happen if you wait too long to click on the + // notification and the app is no longer running in the background. + std::string request; + std::map headers; + std::map message_fields = { + {"message", "This is a notification."}, + {"unique_id", unique_id}, +#if defined(__ANDROID__) + // Duplicate notification.title and notification.body here, see + // below for why. + {"notification_title", kNotificationTitle}, + {"notification_body", kNotificationBody}, +#endif // defined(__ANDROID__) + }; + EXPECT_TRUE(CreateTestMessage(shared_token_->c_str(), kNotificationTitle, + kNotificationBody, message_fields, &request, + &headers)); + std::string html = ConstructHtmlToSendMessage(request, headers, 5); + // We now have some HTML/Javascript to send the message request. Embed it in + // a data: url so we can try receiving a message with the app in the + // background. + // Encode the HTML into base64. + std::string html_encoded; + EXPECT_TRUE(Base64Encode(html, &html_encoded)); + std::string url = std::string("data:text/html;base64,") + html_encoded; + LogInfo("Opening browser to trigger FCM message."); + if (OpenUrlInBrowser(url.c_str())) { + SetPersistentString(kTestingNotificationKey, "1"); + } else { + FAIL() << "Failed to open URL in browser."; + } + } + SetPersistentString(kTestingNotificationKey, nullptr); + LogDebug("Waiting for message."); + firebase::messaging::Message message; + EXPECT_TRUE(WaitForMessage(&message, 120)); + EXPECT_EQ(message.data["unique_id"], unique_id); + EXPECT_TRUE(message.notification_opened); + +#if defined(__ANDROID__) + // On Android, if the app is running in the background, FCM does not deliver + // both the "notification" and the "data". So for our purposes, duplicate the + // notification fields we are checking into the data fields so we can still + // check that it's correct. + EXPECT_EQ(message.notification, nullptr); + EXPECT_EQ(message.data["notification_title"], kNotificationTitle); + EXPECT_EQ(message.data["notification_body"], kNotificationBody); +#else + // On iOS, we do get the notification. + EXPECT_NE(message.notification, nullptr); + if (message.notification) { + EXPECT_EQ(message.notification->title, kNotificationTitle); + EXPECT_EQ(message.notification->body, kNotificationBody); + } +#endif // defined(__ANDROID__) +} + +TEST_F(FirebaseMessagingTest, TestSendMessageToToken) { + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + SKIP_TEST_ON_DESKTOP; + + EXPECT_TRUE(RequestPermission()); + EXPECT_TRUE(WaitForToken()); + std::string unique_id = GetUniqueMessageId(); + const char kNotificationTitle[] = "Token Test"; + const char kNotificationBody[] = "Token Test notification body"; + SendTestMessage(shared_token_->c_str(), kNotificationTitle, kNotificationBody, + {{"message", "Hello, world!"}, + {"unique_id", unique_id}, + {kNotificationLinkKey, kTestLink}}); + LogDebug("Waiting for message."); + firebase::messaging::Message message; + EXPECT_TRUE(WaitForMessage(&message)); + EXPECT_EQ(message.data["unique_id"], unique_id); + EXPECT_NE(message.notification, nullptr); + if (message.notification) { + EXPECT_EQ(message.notification->title, kNotificationTitle); + EXPECT_EQ(message.notification->body, kNotificationBody); + } + EXPECT_EQ(message.link, kTestLink); +} + +TEST_F(FirebaseMessagingTest, TestSendMessageToTopic) { + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + SKIP_TEST_ON_DESKTOP; + + EXPECT_TRUE(RequestPermission()); + EXPECT_TRUE(WaitForToken()); + std::string unique_id = GetUniqueMessageId(); + const char kNotificationTitle[] = "Topic Test"; + const char kNotificationBody[] = "Topic Test notification body"; + // Create a somewhat unique topic name using 2 digits near the end of + // unique_id (but not the LAST 2 digits, due to timestamp resolution on some + // platforms). + std::string unique_id_tag = + (unique_id.length() >= 7 ? unique_id.substr(unique_id.length() - 5, 2) + : "00"); + std::string topic = "FCMTestTopic" + unique_id_tag; + EXPECT_TRUE(WaitForCompletion(firebase::messaging::Subscribe(topic.c_str()), + "Subscribe")); + SendTestMessage(("/topics/" + topic).c_str(), kNotificationTitle, + kNotificationBody, + {{"message", "Hello, world!"}, {"unique_id", unique_id}}); + firebase::messaging::Message message; + EXPECT_TRUE(WaitForMessage(&message)); + EXPECT_EQ(message.data["unique_id"], unique_id); + if (message.notification) { + EXPECT_EQ(message.notification->title, kNotificationTitle); + EXPECT_EQ(message.notification->body, kNotificationBody); + } + + EXPECT_TRUE(WaitForCompletion(firebase::messaging::Unsubscribe(topic.c_str()), + "Unsubscribe")); + + // Ensure that we *don't* receive a message now. + unique_id = GetUniqueMessageId(); + SendTestMessage(("/topics/" + topic).c_str(), "Topic Title 2", "Topic Body 2", + {{"message", "Hello, world!"}, {"unique_id", unique_id}}); + EXPECT_FALSE(WaitForMessage(&message, 5)); +} + +TEST_F(FirebaseMessagingTest, TestChangingListener) { + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + SKIP_TEST_ON_DESKTOP; + + EXPECT_TRUE(RequestPermission()); + EXPECT_TRUE(WaitForToken()); + + // Back up the previous listener object and create a new one. + firebase::messaging::PollableListener* old_listener_ = shared_listener_; + // WaitForMessage() uses whatever shared_listener_ is set to. + shared_listener_ = new firebase::messaging::PollableListener(); + firebase::messaging::SetListener(shared_listener_); + + std::string unique_id = GetUniqueMessageId(); + const char kNotificationTitle[] = "New Listener Test"; + const char kNotificationBody[] = "New Listener Test notification body"; + SendTestMessage(shared_token_->c_str(), kNotificationTitle, kNotificationBody, + {{"message", "Hello, world!"}, {"unique_id", unique_id}}); + LogDebug("Waiting for message."); + firebase::messaging::Message message; + EXPECT_TRUE(WaitForMessage(&message)); + EXPECT_EQ(message.data["unique_id"], unique_id); + if (message.notification) { + EXPECT_EQ(message.notification->title, kNotificationTitle); + EXPECT_EQ(message.notification->body, kNotificationBody); + } + + // Set back to the previous listener. + firebase::messaging::SetListener(old_listener_); + delete shared_listener_; + shared_listener_ = old_listener_; +} + +TEST_F(FirebaseMessagingTest, DeliverMetricsToBigQuery) { + // These setters and getters are not implemented on all platforms, so we run + // them here to make sure they don't crash, and then validate the values + // received below only on the platforms they are implemented on. + + bool initial_value = + firebase::messaging::DeliveryMetricsExportToBigQueryEnabled(); + // This one should always default to false unless it has been set. + EXPECT_FALSE(initial_value); + + firebase::messaging::SetDeliveryMetricsExportToBigQuery(true); + bool result_after_setting = + firebase::messaging::DeliveryMetricsExportToBigQueryEnabled(); + + firebase::messaging::SetDeliveryMetricsExportToBigQuery(false); + bool result_after_clearing = + firebase::messaging::DeliveryMetricsExportToBigQueryEnabled(); + +#if defined(ANDROID) + EXPECT_TRUE(result_after_setting); + EXPECT_FALSE(result_after_clearing); +#else + (void)result_after_setting; + (void)result_after_clearing; +#endif +} + +} // namespace firebase_testapp_automated diff --git a/remote_config/integration_test/AndroidManifest.xml b/remote_config/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..1ce2e9b6e8 --- /dev/null +++ b/remote_config/integration_test/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/remote_config/integration_test/CMakeLists.txt b/remote_config/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..25429e8036 --- /dev/null +++ b/remote_config/integration_test/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_remote_config firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/remote_config/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/remote_config/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/remote_config/integration_test/Images.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/remote_config/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/remote_config/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/remote_config/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/remote_config/integration_test/Info.plist b/remote_config/integration_test/Info.plist new file mode 100644 index 0000000000..e8bfc83986 --- /dev/null +++ b/remote_config/integration_test/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.ios.remoteconfig.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + firebase-game-loop + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/remote_config/integration_test/LaunchScreen.storyboard b/remote_config/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/remote_config/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/remote_config/integration_test/LibraryManifest.xml b/remote_config/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..0f9554e694 --- /dev/null +++ b/remote_config/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/remote_config/integration_test/Podfile b/remote_config/integration_test/Podfile new file mode 100644 index 0000000000..d3fff75f74 --- /dev/null +++ b/remote_config/integration_test/Podfile @@ -0,0 +1,15 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Firebase Remote Config test application. + +target 'integration_test' do + pod 'Firebase/RemoteConfig', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/remote_config/integration_test/build.gradle b/remote_config/integration_test/build.gradle new file mode 100644 index 0000000000..97d20a5449 --- /dev/null +++ b/remote_config/integration_test/build.gradle @@ -0,0 +1,76 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.android.remoteconfig.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + remoteConfig +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/remote_config/integration_test/googletest.cmake b/remote_config/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/remote_config/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/remote_config/integration_test/gradle/wrapper/gradle-wrapper.jar b/remote_config/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/remote_config/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/remote_config/integration_test/gradle/wrapper/gradle-wrapper.properties b/remote_config/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/remote_config/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/remote_config/integration_test/gradlew b/remote_config/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/remote_config/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/remote_config/integration_test/gradlew.bat b/remote_config/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/remote_config/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/remote_config/integration_test/integration_test.xcodeproj/project.pbxproj b/remote_config/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d1d6b852d1 --- /dev/null +++ b/remote_config/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/remote_config/integration_test/proguard.pro b/remote_config/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/remote_config/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/remote_config/integration_test/res/layout/main.xml b/remote_config/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/remote_config/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/remote_config/integration_test/res/values/strings.xml b/remote_config/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..fd7b9680c8 --- /dev/null +++ b/remote_config/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Firebase Remote Config Integration Test + diff --git a/remote_config/integration_test/settings.gradle b/remote_config/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/remote_config/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/remote_config/integration_test/src/integration_test.cc b/remote_config/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..aff4a866d2 --- /dev/null +++ b/remote_config/integration_test/src/integration_test.cc @@ -0,0 +1,430 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/remote_config.h" +#include "firebase/util.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using app_framework::LogDebug; +using app_framework::LogWarning; +using app_framework::ProcessEvents; +using firebase_test_framework::FirebaseTest; +#ifdef FIREBASE_EARLY_ACCESS_PREVIEW +using firebase::Future; +using firebase::remote_config::RemoteConfig; +#endif // FIREBASE_EARLY_ACCESS_PREVIEW + +using testing::UnorderedElementsAre; + +class FirebaseRemoteConfigTest : public FirebaseTest { + public: + FirebaseRemoteConfigTest(); + ~FirebaseRemoteConfigTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + // Initialize Firebase App and Firebase Remote Config. + void Initialize(); + // Shut down Firebase Remote Config and Firebase App. + void Terminate(); + + bool initialized_; +#ifdef FIREBASE_EARLY_ACCESS_PREVIEW + RemoteConfig* rc_; +#endif // FIREBASE_EARLY_ACCESS_PREVIEW +}; + +FirebaseRemoteConfigTest::FirebaseRemoteConfigTest() : initialized_(false) { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +FirebaseRemoteConfigTest::~FirebaseRemoteConfigTest() { + // Must be cleaned up on exit. + assert(app_ == nullptr); +#ifdef FIREBASE_EARLY_ACCESS_PREVIEW + assert(rc_ == nullptr); +#endif // FIREBASE_EARLY_ACCESS_PREVIEW +} + +void FirebaseRemoteConfigTest::SetUp() { + FirebaseTest::SetUp(); + Initialize(); +} + +void FirebaseRemoteConfigTest::TearDown() { + // Delete the shared path, if there is one. + if (initialized_) { + Terminate(); + } + FirebaseTest::TearDown(); +} + +void FirebaseRemoteConfigTest::Initialize() { + if (initialized_) return; + SetLogLevel(app_framework::kDebug); + + InitializeApp(); + + LogDebug("Initializing Firebase Remote Config."); + + ::firebase::ModuleInitializer initializer; + + void* ptr = nullptr; +#ifdef FIREBASE_EARLY_ACCESS_PREVIEW + ptr = &rc_; +#endif + initializer.Initialize(app_, ptr, [](::firebase::App* app, void* target) { + LogDebug("Try to initialize Firebase RemoteConfig"); +#ifdef FIREBASE_EARLY_ACCESS_PREVIEW + RemoteConfig** rc_ptr = reinterpret_cast(target); + *rc_ptr = RemoteConfig::GetInstance(app); +#endif + return firebase::remote_config::Initialize(*app); + }); + + WaitForCompletion(initializer.InitializeLastResult(), "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase RemoteConfig."); + + initialized_ = true; +} + +void FirebaseRemoteConfigTest::Terminate() { + if (!initialized_) return; + + LogDebug("Shutdown the Remote Config library."); +#ifdef FIREBASE_EARLY_ACCESS_PREVIEW + if (rc_) { + delete rc_; + rc_ = nullptr; + } +#endif // FIREBASE_EARLY_ACCESS_PREVIEW + LogDebug("DTerminating."); + firebase::remote_config::Terminate(); + TerminateApp(); + + initialized_ = false; + + ProcessEvents(100); +} + +static const char* ValueSourceToString( + firebase::remote_config::ValueSource source) { + static const char* kSourceToString[] = { + "Static", // kValueSourceStaticValue + "Remote", // kValueSourceRemoteValue + "Default", // kValueSourceDefaultValue + }; + return kSourceToString[source]; +} + +static const unsigned char kBinaryDefaults[] = {6, 0, 0, 6, 7, 3}; +// NOLINTNEXTLINE +static const firebase::remote_config::ConfigKeyValueVariant defaults[] = { + {"TestBoolean", false}, + {"TestLong", 42}, + {"TestDouble", 3.14}, + {"TestString", "Hello World"}, + {"TestData", firebase::Variant::FromStaticBlob(kBinaryDefaults, + sizeof(kBinaryDefaults))}, + {"TestDefaultOnly", "Default value that won't be overridden"}}; + +// TestData 4321 +// TestDouble 625.63 +// TestLong 119 +// TestBoolean true +// TestString This is a string +// NOLINTNEXTLINE +static const firebase::remote_config::ConfigKeyValueVariant kServerValue[] = { + {"TestBoolean", true}, + {"TestLong", 119}, + {"TestDouble", 625.63}, + {"TestString", firebase::Variant::FromMutableString("This is a string")}, + {"TestData", 4321}, + {"TestDefaultOnly", firebase::Variant::FromMutableString( + "Default value that won't be overridden")}}; + +static void SetDefaults() { + size_t default_count = FIREBASE_ARRAYSIZE(defaults); + firebase::remote_config::SetDefaults(defaults, default_count); +} + +#ifdef FIREBASE_EARLY_ACCESS_PREVIEW +static Future SetDefaultsV2(RemoteConfig* rc) { + size_t default_count = FIREBASE_ARRAYSIZE(defaults); + return rc->SetDefaults(defaults, default_count); +} +#endif // FIREBASE_EARLY_ACCESS_PREVIEW + +// Test cases below. + +TEST_F(FirebaseRemoteConfigTest, TestInitializeAndTerminate) { + // Already tested via SetUp() and TearDown(). +} + +TEST_F(FirebaseRemoteConfigTest, TestSetDefaults) { + SetDefaults(); + + bool validated_defaults = true; + firebase::remote_config::ValueInfo value_info; + bool bool_value = + firebase::remote_config::GetBoolean("TestBoolean", &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_FALSE(bool_value); + } else { + validated_defaults = false; + } + int64_t int64_value = + firebase::remote_config::GetLong("TestLong", &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_EQ(int64_value, 42); + } else { + validated_defaults = false; + } + double double_value = + firebase::remote_config::GetDouble("TestDouble", &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_NEAR(double_value, 3.14, 0.0001); + } else { + validated_defaults = false; + } + std::string string_value = + firebase::remote_config::GetString("TestString", &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_EQ(string_value, "Hello World"); + } else { + validated_defaults = false; + } + std::vector blob_value = + firebase::remote_config::GetData("TestData"); //, &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_THAT(blob_value, testing::ElementsAreArray(kBinaryDefaults, + sizeof(kBinaryDefaults))); + } else { + validated_defaults = false; + } + + if (!validated_defaults) { + LogWarning( + "Can't validate defaults, they've been overridden by server values.\n" +#if defined(__ANDROID__) + "Delete the app's data and run this test again to test SetDefaults:\n" + " adb shell pm clear [bundle ID]" +#elif defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE + "Uninstall and re-install the app and run this again to test " + "SetDefaults." +#else // Desktop + "Delete the Remote Config cache and run this test again to test " + "SetDefaults:\n" +#if defined(_WIN32) + " del remote_config_data" +#else + " rm remote_config_data" +#endif // defined(_WIN32) +#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE + ); + } +} + +/* The following test expects that you have your server values set to: + TestData 4321 + TestDouble 625.63 + TestLong 119 + TestBoolean true + TestString This is a string + */ + +TEST_F(FirebaseRemoteConfigTest, TestFetchAndActivate) { + SetDefaults(); + + WaitForCompletion(firebase::remote_config::Fetch(0), "Fetch"); + EXPECT_TRUE(firebase::remote_config::ActivateFetched()); + const firebase::remote_config::ConfigInfo& info = + firebase::remote_config::GetInfo(); + LogDebug("Fetch time: %lld", info.fetch_time); + firebase::remote_config::ValueInfo value_info; + bool bool_value = + firebase::remote_config::GetBoolean("TestBoolean", &value_info); + EXPECT_EQ(value_info.source, firebase::remote_config::kValueSourceRemoteValue) + << "TestBoolean source is " << ValueSourceToString(value_info.source) + << ", expected Remote"; + EXPECT_TRUE(bool_value); + + int64_t int64_value = + firebase::remote_config::GetLong("TestLong", &value_info); + EXPECT_EQ(value_info.source, firebase::remote_config::kValueSourceRemoteValue) + << "TestLong source is " << ValueSourceToString(value_info.source) + << ", expected Remote"; + EXPECT_EQ(int64_value, 119); + + double double_value = + firebase::remote_config::GetDouble("TestDouble", &value_info); + EXPECT_EQ(value_info.source, firebase::remote_config::kValueSourceRemoteValue) + << "TestDouble source is " << ValueSourceToString(value_info.source) + << ", expected Remote"; + EXPECT_NEAR(double_value, 625.63, 0.0001); + + std::string string_value = + firebase::remote_config::GetString("TestString", &value_info); + EXPECT_EQ(value_info.source, firebase::remote_config::kValueSourceRemoteValue) + << "TestString source is " << ValueSourceToString(value_info.source) + << ", expected Remote"; + EXPECT_EQ(string_value, "This is a string"); + + std::vector blob_value = + firebase::remote_config::GetData("TestData"); //, &value_info); + EXPECT_EQ(value_info.source, firebase::remote_config::kValueSourceRemoteValue) + << "TestData source is " << ValueSourceToString(value_info.source) + << ", expected Remote"; + + const unsigned char kExpectedBlobServerValue[] = {'4', '3', '2', '1'}; + EXPECT_THAT(blob_value, + testing::ElementsAreArray(kExpectedBlobServerValue, + sizeof(kExpectedBlobServerValue))); +} + +TEST_F(FirebaseRemoteConfigTest, TestGetKeys) { + SetDefaults(); + + std::vector keys = firebase::remote_config::GetKeys(); + EXPECT_THAT( + keys, UnorderedElementsAre("TestBoolean", "TestLong", "TestDouble", + "TestString", "TestData", "TestDefaultOnly")); + std::vector keys_subset = + firebase::remote_config::GetKeysByPrefix("TestD"); + EXPECT_THAT(keys_subset, UnorderedElementsAre("TestDouble", "TestData", + "TestDefaultOnly")); +} + +#if defined(FIREBASE_EARLY_ACCESS_PREVIEW) && \ + (defined(__ANDROID__) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE)) +TEST_F(FirebaseRemoteConfigTest, TestSetDefaultsV2) { + EXPECT_TRUE(WaitForCompletion(SetDefaultsV2(rc_), "SetDefaultsV2")); + + bool validated_defaults = true; + firebase::remote_config::ValueInfo value_info; + bool bool_value = rc_->GetBoolean("TestBoolean", &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_FALSE(bool_value); + } else { + validated_defaults = false; + } + int64_t int64_value = rc_->GetLong("TestLong", &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_EQ(int64_value, 42); + } else { + validated_defaults = false; + } + double double_value = rc_->GetDouble("TestDouble", &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_NEAR(double_value, 3.14, 0.0001); + } else { + validated_defaults = false; + } + std::string string_value = rc_->GetString("TestString", &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_EQ(string_value, "Hello World"); + } else { + validated_defaults = false; + } + std::vector blob_value = + rc_->GetData("TestData"); //, &value_info); + if (value_info.source == firebase::remote_config::kValueSourceDefaultValue) { + EXPECT_THAT(blob_value, testing::ElementsAreArray(kBinaryDefaults, + sizeof(kBinaryDefaults))); + } else { + validated_defaults = false; + } + + if (!validated_defaults) { + LogWarning( + "Can't validate defaults, they've been overridden by server values.\n" +#if defined(__ANDROID__) + "Delete the app's data and run this test again to test SetDefaults:\n" + " adb shell pm clear [bundle ID]" +#elif defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE + "Uninstall and re-install the app and run this again to test " + "SetDefaults." +#else // Desktop + "Delete the Remote Config cache and run this test again to test " + "SetDefaults:\n" +#if defined(_WIN32) + " del remote_config_data" +#else + " rm remote_config_data" +#endif // defined(_WIN32) +#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE + ); + } +} + +TEST_F(FirebaseRemoteConfigTest, TestGetKeysV2) { + EXPECT_TRUE(WaitForCompletion(SetDefaultsV2(rc_), "SetDefaultsV2")); + + std::vector keys = rc_->GetKeys(); + EXPECT_THAT( + keys, UnorderedElementsAre("TestBoolean", "TestLong", "TestDouble", + "TestString", "TestData", "TestDefaultOnly")); + std::vector keys_subset = + firebase::remote_config::GetKeysByPrefix("TestD"); + EXPECT_THAT(keys_subset, UnorderedElementsAre("TestDouble", "TestData", + "TestDefaultOnly")); +} + +TEST_F(FirebaseRemoteConfigTest, TestGetAll) { + EXPECT_TRUE(WaitForCompletion(SetDefaultsV2(rc_), "SetDefaultsV2")); + + std::map key_values = rc_->GetAll(); + EXPECT_EQ(key_values.size(), 6); + + for (auto key_valur_pair : kServerValue) { + firebase::Variant k_value = key_valur_pair.value; + firebase::Variant fetched_value = key_values[key_valur_pair.key]; + EXPECT_EQ(k_value.type(), fetched_value.type()); + EXPECT_EQ(k_value, fetched_value); + } +} + +#endif // FIREBASE_EARLY_ACCESS_PREVIEW + +} // namespace firebase_testapp_automated diff --git a/setup_integration_tests.py b/setup_integration_tests.py new file mode 100755 index 0000000000..895c38fc69 --- /dev/null +++ b/setup_integration_tests.py @@ -0,0 +1,58 @@ +#!/usr/bin/python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Copy files from test & sample frameworks into integration_test directories. + +Usage: %s [destination directories] +""" + +import os +import sys +from distutils.dir_util import copy_tree + +# Where to copy framework files from, relative to this script's location. +FRAMEWORK_DIRECTORIES = [ + 'testing/sample_framework', + 'testing/test_framework', +] + +# If running without specifying a directory, these are the destination +# directories to use instead, relative to this script's location. +DEFAULT_DESTINATIONS = [ + 'admob/integration_test', + 'analytics/integration_test', + 'app/integration_test', + 'auth/integration_test', + 'database/integration_test', + 'dynamic_links/integration_test', + 'firestore/integration_test', + 'functions/integration_test', + 'instance_id/integration_test', + 'messaging/integration_test', + 'remote_config/integration_test', + 'storage/integration_test', +] + +destinations = sys.argv[1:] if len(sys.argv) > 1 else DEFAULT_DESTINATIONS +destinations = map(os.path.abspath, destinations) +os.chdir(os.path.dirname(os.path.abspath(__file__))) + +for dest in destinations: + if not os.path.exists(dest): + print('Error, destination path "%s" does not exist' % dest) + continue + for src in FRAMEWORK_DIRECTORIES: + print('Copying %s to %s' % (src, dest)) + copy_tree(src, dest, update=1) diff --git a/storage/integration_test/AndroidManifest.xml b/storage/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..2f834feea2 --- /dev/null +++ b/storage/integration_test/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/storage/integration_test/CMakeLists.txt b/storage/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..8aef4ebf70 --- /dev/null +++ b/storage/integration_test/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process(COMMAND "python" "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" "${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Integration test uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + +# Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_storage firebase_auth firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/storage/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/storage/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/storage/integration_test/Images.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/storage/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/storage/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/storage/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/storage/integration_test/Info.plist b/storage/integration_test/Info.plist new file mode 100644 index 0000000000..c112525414 --- /dev/null +++ b/storage/integration_test/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.firebase.cpp.storage.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + firebase-game-loop + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/storage/integration_test/LaunchScreen.storyboard b/storage/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/storage/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/storage/integration_test/LibraryManifest.xml b/storage/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..18192be20a --- /dev/null +++ b/storage/integration_test/LibraryManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/storage/integration_test/Podfile b/storage/integration_test/Podfile new file mode 100644 index 0000000000..923e89d1c8 --- /dev/null +++ b/storage/integration_test/Podfile @@ -0,0 +1,16 @@ + +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +# Cloud Storage for Firebase test application. + +target 'integration_test' do + pod 'Firebase/Storage', '6.24.0' + pod 'Firebase/Auth', '6.24.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python ../../setup_integration_tests.py .; fi") + system("/usr/bin/python ./download_googletest.py") +end + diff --git a/storage/integration_test/build.gradle b/storage/integration_test/build.gradle new file mode 100644 index 0000000000..768d3ffda6 --- /dev/null +++ b/storage/integration_test/build.gradle @@ -0,0 +1,77 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.firebase.cpp.storage.testapp' + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth + storage +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) \ No newline at end of file diff --git a/storage/integration_test/googletest.cmake b/storage/integration_test/googletest.cmake new file mode 100644 index 0000000000..2ecbb08679 --- /dev/null +++ b/storage/integration_test/googletest.cmake @@ -0,0 +1,34 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Download GoogleTest from GitHub as an external project. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/storage/integration_test/gradle/wrapper/gradle-wrapper.jar b/storage/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/storage/integration_test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/storage/integration_test/gradle/wrapper/gradle-wrapper.properties b/storage/integration_test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..35732b09a0 --- /dev/null +++ b/storage/integration_test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/storage/integration_test/gradlew b/storage/integration_test/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/storage/integration_test/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/storage/integration_test/gradlew.bat b/storage/integration_test/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/storage/integration_test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/storage/integration_test/integration_test.xcodeproj/project.pbxproj b/storage/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d1d6b852d1 --- /dev/null +++ b/storage/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/storage/integration_test/proguard.pro b/storage/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/storage/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/storage/integration_test/readme.md b/storage/integration_test/readme.md new file mode 100644 index 0000000000..6b9efa6fcd --- /dev/null +++ b/storage/integration_test/readme.md @@ -0,0 +1,215 @@ +Cloud Storage for Firebase Quickstart +======================== + +The Cloud Storage for Firebase Integration Test (integration test) demonstrates +Cloud Storage operations with the Firebase C++ SDK for Cloud Storage. +The application has no user interface and simply logs actions it's performing +to the console. + +The integration test uses the GoogleTest testing framework to perform a variety +of tests on a live Firebase project. Tests include: + - Creating a firebase::App in a platform-specific way. The App holds + platform-specific context that's used by other Firebase APIs, and is a + central point for communication between the Cloud Storage C++ and + Firebase Auth C++ libraries. + - Getting a pointer to firebase::Auth, and signs in anonymously. This allows the + integration test to access a Cloud Storage instance with authentication rules + enabled, which is the default setting in Firebase Console. + - Gets a StorageReference to a test-specific storage node, uses + StorageReference::Child() to create a child with a unique key based on the + current time in microseconds to work in, and gets a reference to that child, + which the integration test will use for the remainder of its actions. + - Uploads some sample files and reads them back to ensure the storage can be + read from and written to. + - Checks the Metadata of the uploaded and downloaded files to ensure they + return the expected values for things like size and date modified. + - Disconnects and then reconnects and verifies it still has access to the + files uploaded. + - Shuts down the Cloud Storage, Firebase Auth, and Firebase App systems, + ensuring the systems can be shut down in any order. + +Introduction +------------ + +- [Read more about Cloud Storage for Firebase](https://firebase.google.com/docs/storage/) + +Building and Running the integration test +----------------------------------------- + +### iOS + - Link your iOS app to the Firebase libraries. + - Get CocoaPods version 1 or later by running, + ``` + sudo gem install cocoapods --pre + ``` + - From the integration_tests/storage directory, install the CocoaPods listed + in the Podfile by running, + ``` + pod install + ``` + - Open the generated Xcode workspace (which now has the CocoaPods), + ``` + open integration_test.xcworkspace + ``` + - For further details please refer to the + [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). + - Register your iOS app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), and attach + your iOS app to it. + - You can use "com.google.firebase.cpp.storage.testapp" as the iOS Bundle + ID while you're testing. You can omit App Store ID while testing. + - Add the GoogleService-Info.plist that you downloaded from Firebase console + to the integration_test/storage directory. This file identifies your iOS + app to the Firebase backend. + - In the Firebase console for your app, select "Auth", then enable + "Anonymous". This will allow the integration test to use anonymous sign-in to + authenticate with Cloud Storage, which requires a signed-in user by + default (an anonymous user will suffice). + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Add the following frameworks from the Firebase C++ SDK to the project: + - frameworks/ios/universal/firebase.framework + - frameworks/ios/universal/firebase_auth.framework + - frameworks/ios/universal/firebase_storage.framework + - You will need to either, + 1. Check "Copy items if needed" when adding the frameworks, or + 2. Add the framework path in "Framework Search Paths" + - e.g. If you downloaded the Firebase C++ SDK to + `/Users/me/firebase_cpp_sdk`, + then you would add the path + `/Users/me/firebase_cpp_sdk/frameworks/ios/universal`. + - To add the path, in XCode, select your project in the project + navigator, then select your target in the main window. + Select the "Build Settings" tab, and click "All" to see all + the build settings. Scroll down to "Search Paths", and add + your path to "Framework Search Paths". + - In XCode, build & run the sample on an iOS device or simulator. + - The integration test has no interativity. The output of the app can be + viewed via the console or on the device's display. In Xcode, select "View + --> Debug Area --> Activate Console" from the menu to view the console. + +### Android + - Register your Android app with Firebase. + - Create a new app on + the [Firebase console](https://firebase.google.com/console/), and attach + your Android app to it. + - You can use "com.google.firebase.cpp.storage.testapp" as the Package + Name while you're testing. + - To + [generate a SHA1](https://developers.google.com/android/guides/client-auth) + run this command on Mac and Linux, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore + ``` + or this command on Windows, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore + ``` + - If keytool reports that you do not have a debug.keystore, you can + [create one with](http://developer.android.com/tools/publishing/app-signing.html#signing-manually), + ``` + keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" + ``` + - Add the `google-services.json` file that you downloaded from Firebase + console to the integration_test/storage directory. This file identifies + your Android app to the Firebase backend. + - In the Firebase console for your app, select "Auth", then enable + "Anonymous". This will allow the integration test to use anonymous sign-in + to authenticate with Cloud Storage, which requires a signed-in user by + default (an anonymous user will suffice). + - For further details please refer to the + [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the location of the Firebase C++ SDK by setting the + firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. + For example, in the project directory: + ``` + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + ``` + - Ensure the Android SDK and NDK locations are set in Android Studio. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. + - Open *build.gradle* in Android Studio. + - From the Android Studio launch menu, "Open an existing Android Studio + project", and select `build.gradle`. + - Install the SDK Platforms that Android Studio reports missing. + - Build the integration test and run it on an Android device or emulator. + - Once you've installed the SDKs in Android Studio, you can build the + integration test in Android Studio, or from the command-line by running + `./gradlew build`. + - The integration test has no interactive interface. The output of the app can + be viewed on the device's display, or in the logcat output of Android studio + or by running "adb logcat *:W android_main firebase" from the command line. + +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the integration_test/storage + directory. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the integration_test/storage directory . + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the integration test with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the integration_test/storage directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the integration test, by either opening the generated project file + based on the platform, or running, + ``` + cmake --build . + ``` + - Execute the integration test by running, + ``` + ./integration_test + ``` + Note that the executable might be under another directory, such as Debug. + - The integration test has no user interface, but the output can be viewed via + the console. + +Support +------- + +[https://firebase.google.com/support/](https://firebase.google.com/support/) + +License +------- + +Copyright 2016 Google, Inc. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you 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. diff --git a/storage/integration_test/res/layout/main.xml b/storage/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..d3ffb63082 --- /dev/null +++ b/storage/integration_test/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/storage/integration_test/res/values/strings.xml b/storage/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..6d72216f3b --- /dev/null +++ b/storage/integration_test/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Cloud Storage for Firebase Integration Test + diff --git a/storage/integration_test/settings.gradle b/storage/integration_test/settings.gradle new file mode 100644 index 0000000000..e30c259ab6 --- /dev/null +++ b/storage/integration_test/settings.gradle @@ -0,0 +1,39 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((new File('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = new File('../..').absolutePath + } + else if ((new File('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild "$firebase_cpp_sdk_dir" \ No newline at end of file diff --git a/storage/integration_test/src/integration_test.cc b/storage/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..80f193cce2 --- /dev/null +++ b/storage/integration_test/src/integration_test.cc @@ -0,0 +1,776 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/auth.h" +#include "firebase/internal/platform.h" +#include "firebase/storage.h" +#include "firebase/util.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using app_framework::LogDebug; + +// You can customize the Storage URL here. +const char* kStorageUrl = nullptr; + +#if FIREBASE_PLATFORM_DESKTOP +// Use a larger file on desktop... +const int kLargeFileMegabytes = 25; +#else +// ...and a smaller file on mobile. +const int kLargeFileMegabytes = 10; +#endif + +const char kRootNodeName[] = "integration_test_data"; + +using app_framework::GetCurrentTimeInMicroseconds; +using app_framework::PathForResource; +using app_framework::ProcessEvents; +using firebase_test_framework::FirebaseTest; +using testing::ElementsAreArray; + +class FirebaseStorageTest : public FirebaseTest { + public: + FirebaseStorageTest(); + ~FirebaseStorageTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + // Initialize Firebase App, Firebase Auth, and Firebase Storage. + void Initialize(); + // Shut down Firebase Storage, Firebase Auth, and Firebase App. + void Terminate(); + // Sign in an anonymous user. + void SignIn(); + // Create a unique working folder and return a reference to it. + firebase::storage::StorageReference CreateFolder(); + + bool initialized_; + firebase::auth::Auth* auth_; + firebase::storage::Storage* storage_; + // File references that we need to delete on test exit. + std::vector cleanup_files_; + std::string saved_url_; +}; + +FirebaseStorageTest::FirebaseStorageTest() + : initialized_(false), auth_(nullptr), storage_(nullptr) { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +FirebaseStorageTest::~FirebaseStorageTest() { + // Must be cleaned up on exit. + assert(app_ == nullptr); + assert(auth_ == nullptr); + assert(storage_ == nullptr); +} + +void FirebaseStorageTest::SetUp() { + FirebaseTest::SetUp(); + Initialize(); +} + +void FirebaseStorageTest::TearDown() { + if (initialized_) { + Terminate(); + } + FirebaseTest::TearDown(); +} + +void FirebaseStorageTest::Initialize() { + if (initialized_) return; + + InitializeApp(); + + LogDebug("Initializing Firebase Auth and Cloud Storage."); + + // 0th element has a reference to this object, the rest have the initializer + // targets. + void* initialize_targets[] = {&auth_, &storage_}; + + const firebase::ModuleInitializer::InitializerFn initializers[] = { + [](::firebase::App* app, void* data) { + void** targets = reinterpret_cast(data); + LogDebug("Attempting to initialize Firebase Auth."); + ::firebase::InitResult result; + *reinterpret_cast<::firebase::auth::Auth**>(targets[0]) = + ::firebase::auth::Auth::GetAuth(app, &result); + return result; + }, + [](::firebase::App* app, void* data) { + void** targets = reinterpret_cast(data); + LogDebug("Attempting to initialize Cloud Storage."); + ::firebase::InitResult result; + firebase::storage::Storage* storage = + firebase::storage::Storage::GetInstance(app, kStorageUrl, &result); + *reinterpret_cast<::firebase::storage::Storage**>(targets[1]) = storage; + return result; + }}; + + ::firebase::ModuleInitializer initializer; + initializer.Initialize(app_, initialize_targets, initializers, + sizeof(initializers) / sizeof(initializers[0])); + + WaitForCompletion(initializer.InitializeLastResult(), "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase Auth and Cloud Storage."); + + initialized_ = true; +} + +void FirebaseStorageTest::Terminate() { + if (!initialized_) return; + + if (storage_) { + LogDebug("Cleaning up files."); + std::vector> cleanups; + cleanups.reserve(cleanup_files_.size()); + for (int i = 0; i < cleanup_files_.size(); ++i) { + cleanups.push_back(cleanup_files_[i].Delete()); + } + for (int i = 0; i < cleanups.size(); ++i) { + WaitForCompletion(cleanups[i], "FirebaseStorageTest::TearDown"); + } + cleanup_files_.clear(); + + LogDebug("Shutdown the Storage library."); + delete storage_; + storage_ = nullptr; + } + if (auth_) { + LogDebug("Shutdown the Auth library."); + delete auth_; + auth_ = nullptr; + } + + TerminateApp(); + + initialized_ = false; + + ProcessEvents(100); +} + +void FirebaseStorageTest::SignIn() { + LogDebug("Signing in."); + firebase::Future sign_in_future = + auth_->SignInAnonymously(); + WaitForCompletion(sign_in_future, "SignInAnonymously"); + if (sign_in_future.error() != 0) { + FAIL() << "Ensure your application has the Anonymous sign-in provider " + "enabled in Firebase Console."; + } + ProcessEvents(100); +} + +firebase::storage::StorageReference FirebaseStorageTest::CreateFolder() { + // Generate a folder for the test data based on the time in milliseconds. + int64_t time_in_microseconds = GetCurrentTimeInMicroseconds(); + + char buffer[21] = {0}; + snprintf(buffer, sizeof(buffer), "%lld", + static_cast(time_in_microseconds)); // NOLINT + saved_url_ = buffer; + return storage_->GetReference(kRootNodeName).Child(saved_url_); +} + +// Test cases below. + +TEST_F(FirebaseStorageTest, TestInitializeAndTerminate) { + // Already tested via SetUp() and TearDown(). +} + +TEST_F(FirebaseStorageTest, TestSignIn) { + SignIn(); + EXPECT_NE(auth_->current_user(), nullptr); +} + +TEST_F(FirebaseStorageTest, TestCreateWorkingFolder) { + SignIn(); + // Create a unique child in the storage that we can run our tests in. + firebase::storage::StorageReference ref = CreateFolder(); + EXPECT_NE(saved_url_, ""); + + LogDebug("Storage URL: gs://%s%s", ref.bucket().c_str(), + ref.full_path().c_str()); + // Create the same reference in a few different manners and ensure they're + // equivalent. + // NOLINTNEXTLINE intentional redundant string conversion + { + firebase::storage::StorageReference ref_from_str = + storage_->GetReference(std::string(kRootNodeName)).Child(saved_url_); + EXPECT_EQ(ref.bucket(), ref_from_str.bucket()); + EXPECT_EQ(ref.full_path(), ref_from_str.full_path()); + } + std::string url = "gs://" + ref.bucket() + "/" + kRootNodeName; + LogDebug("Calling GetReferenceFromUrl(%s)", url.c_str()); + firebase::storage::StorageReference ref_from_url = + storage_->GetReferenceFromUrl(url.c_str()).Child(saved_url_); + EXPECT_TRUE(ref_from_url.is_valid()); + EXPECT_EQ(ref.bucket(), ref_from_url.bucket()); + EXPECT_EQ(ref.full_path(), ref_from_url.full_path()); + firebase::storage::StorageReference ref_from_url_str = + storage_->GetReferenceFromUrl(url).Child(saved_url_); + EXPECT_TRUE(ref_from_url_str.is_valid()); + EXPECT_EQ(ref.bucket(), ref_from_url_str.bucket()); + EXPECT_EQ(ref.full_path(), ref_from_url_str.full_path()); +} + +TEST_F(FirebaseStorageTest, TestStorageUrl) { + SignIn(); + // Confirm that creating a storage instance with a URL returns a url(), and + // creating a storage instance with a null URL returns a blank url(). + std::string default_url = + std::string("gs://") + storage_->GetReference().bucket(); + + // Check whether the Storage instance we already have is handled correctly. + EXPECT_EQ(storage_->url(), kStorageUrl ? kStorageUrl : ""); + delete storage_; + storage_ = nullptr; + { + firebase::storage::Storage* storage_explicit = + firebase::storage::Storage::GetInstance(app_, default_url.c_str(), + nullptr); + ASSERT_NE(storage_explicit, nullptr); + EXPECT_EQ(storage_explicit->url(), default_url); + delete storage_explicit; + } + { + firebase::storage::Storage* storage_implicit = + firebase::storage::Storage::GetInstance(app_, nullptr, nullptr); + ASSERT_NE(storage_implicit, nullptr); + EXPECT_EQ(storage_implicit->url(), ""); + delete storage_implicit; + } +} + +// NOLINTNEXTLINE +const std::string kSimpleTestFile = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim " + "ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " + "aliquip ex ea commodo consequat. Duis aute irure dolor in " + "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " + "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " + "culpa qui officia deserunt mollit anim id est laborum."; + +TEST_F(FirebaseStorageTest, TestWriteAndReadByteBuffer) { + SignIn(); + + firebase::storage::StorageReference ref = + CreateFolder().Child("TestFile.txt"); + LogDebug("Storage URL: gs://%s%s", ref.bucket().c_str(), + ref.full_path().c_str()); + cleanup_files_.push_back(ref); + // Write to a simple file. + { + LogDebug("Upload sample file from memory."); + firebase::Future future = + ref.PutBytes(&kSimpleTestFile[0], kSimpleTestFile.size()); + WaitForCompletion(future, "PutBytes"); + auto metadata = future.result(); + EXPECT_EQ(metadata->size_bytes(), kSimpleTestFile.size()); + } + + // Now read back the file. + { + LogDebug("Download sample file to memory."); + const size_t kBufferSize = 1024; + char buffer[kBufferSize]; + memset(buffer, 0, sizeof(buffer)); + + firebase::Future future = ref.GetBytes(buffer, kBufferSize); + WaitForCompletion(future, "GetBytes"); + ASSERT_NE(future.result(), nullptr); + size_t file_size = *future.result(); + EXPECT_EQ(file_size, kSimpleTestFile.size()); + EXPECT_THAT(kSimpleTestFile, ElementsAreArray(buffer, file_size)) + << "Download failed, file contents did not match."; + } +} + +TEST_F(FirebaseStorageTest, TestWriteAndReadFileWithCustomMetadata) { + SignIn(); + + firebase::storage::StorageReference ref = + CreateFolder().Child("TestFile-CustomMetadata.txt"); + LogDebug("Storage URL: gs://%s%s", ref.bucket().c_str(), + ref.full_path().c_str()); + cleanup_files_.push_back(ref); + std::string content_type = "text/plain"; + std::string custom_metadata_key = "specialkey"; + std::string custom_metadata_value = "secret value"; + // Write to a simple file. + { + LogDebug("Write a sample file with custom metadata from byte buffer."); + firebase::storage::Metadata metadata; + metadata.set_content_type(content_type.c_str()); + (*metadata.custom_metadata())[custom_metadata_key] = custom_metadata_value; + firebase::Future future = + ref.PutBytes(&kSimpleTestFile[0], kSimpleTestFile.size(), metadata); + WaitForCompletion(future, "PutBytes"); + const firebase::storage::Metadata* metadata_written = future.result(); + ASSERT_NE(metadata_written, nullptr); + EXPECT_EQ(metadata_written->size_bytes(), kSimpleTestFile.size()); + EXPECT_EQ(metadata_written->content_type(), content_type); + auto& custom_metadata = *metadata_written->custom_metadata(); + EXPECT_EQ(custom_metadata[custom_metadata_key], custom_metadata_value); + } + // Now read back the file. + { + LogDebug("Download sample file with custom metadata to memory."); + const size_t kBufferSize = 1024; + char buffer[kBufferSize]; + memset(buffer, 0, sizeof(buffer)); + + firebase::Future future = ref.GetBytes(buffer, kBufferSize); + WaitForCompletion(future, "GetBytes"); + ASSERT_NE(future.result(), nullptr); + size_t file_size = *future.result(); + EXPECT_EQ(file_size, kSimpleTestFile.size()); + EXPECT_THAT(kSimpleTestFile, ElementsAreArray(buffer, file_size)) + << "Download failed, file contents did not match."; + } + // And read the custom metadata. + { + LogDebug("Read custom metadata."); + firebase::Future future = ref.GetMetadata(); + WaitForCompletion(future, "GetFileMetadata"); + const firebase::storage::Metadata* metadata = future.result(); + ASSERT_NE(metadata, nullptr); + + // Get the current time to compare to the Timestamp. + int64_t current_time_seconds = static_cast(time(nullptr)); + int64_t updated_time_milliseconds = metadata->updated_time(); + int64_t updated_time_seconds = updated_time_milliseconds / 1000; + int64_t time_difference_seconds = + updated_time_seconds - current_time_seconds; + // As long as our timestamp is within a day, it's correct enough for + // our purposes. + const int64_t kAllowedTimeDifferenceSeconds = 60L * 60L * 24L; + EXPECT_TRUE(time_difference_seconds < kAllowedTimeDifferenceSeconds && + time_difference_seconds > -kAllowedTimeDifferenceSeconds) + << "Bad timestamp in metadata."; + EXPECT_EQ(metadata->size_bytes(), kSimpleTestFile.size()); + EXPECT_EQ(metadata->content_type(), content_type); + ASSERT_NE(metadata->custom_metadata(), nullptr); + auto& custom_metadata = *metadata->custom_metadata(); + EXPECT_EQ(custom_metadata[custom_metadata_key], custom_metadata_value); + } +} + +const char kPutFileTestFile[] = "PutFileTest.txt"; +const char kGetFileTestFile[] = "GetFileTest.txt"; +const char kFileUriScheme[] = "file://"; + +TEST_F(FirebaseStorageTest, TestPutFileAndGetFile) { + SignIn(); + firebase::storage::StorageReference ref = + CreateFolder().Child("TestFile-FileIO.txt"); + cleanup_files_.push_back(ref); + + // Upload a file. + { + // Write file that we're going to upload. + std::string path = PathForResource() + kPutFileTestFile; + // Cloud Storage expects a URI, so add file:// in front of local paths. + std::string file_path = kFileUriScheme + path; + + LogDebug("Creating local file: %s", path.c_str()); + + FILE* file = fopen(path.c_str(), "wb"); + std::fwrite(kSimpleTestFile.c_str(), 1, kSimpleTestFile.size(), file); + fclose(file); + + firebase::storage::Metadata new_metadata; + std::string content_type = "text/plain"; + new_metadata.set_content_type(content_type); + + LogDebug("Uploading sample file from disk."); + firebase::Future future = + ref.PutFile(file_path.c_str(), new_metadata); + WaitForCompletion(future, "PutFile"); + ASSERT_NE(future.result(), nullptr); + const firebase::storage::Metadata* metadata = future.result(); + EXPECT_EQ(metadata->size_bytes(), kSimpleTestFile.size()); + EXPECT_EQ(metadata->content_type(), content_type); + } + + // Use GetBytes to ensure the file uploaded correctly. + { + LogDebug("Downloading file to disk."); + const size_t kBufferSize = 1024; + char buffer[kBufferSize]; + memset(buffer, 0, sizeof(buffer)); + + firebase::Future future = ref.GetBytes(buffer, kBufferSize); + WaitForCompletion(future, "GetBytes"); + ASSERT_NE(future.result(), nullptr); + size_t file_size = *future.result(); + EXPECT_EQ(file_size, kSimpleTestFile.size()); + EXPECT_THAT(kSimpleTestFile, ElementsAreArray(buffer, file_size)) + << "Read file to byte buffer failed, file contents did not match."; + } + // Test GetFile to ensure we can download to a file. + { + std::string path = PathForResource() + kGetFileTestFile; + // Cloud Storage expects a URI, so add file:// in front of local paths. + std::string file_path = kFileUriScheme + path; + + LogDebug("Saving to local file: %s", path.c_str()); + + firebase::Future future = ref.GetFile(file_path.c_str()); + WaitForCompletion(future, "GetFile"); + ASSERT_NE(future.result(), nullptr); + EXPECT_EQ(*future.result(), kSimpleTestFile.size()); + + std::vector buffer(kSimpleTestFile.size()); + FILE* file = fopen(path.c_str(), "rb"); + ASSERT_NE(file, nullptr); + std::fread(&buffer[0], 1, kSimpleTestFile.size(), file); + fclose(file); + EXPECT_THAT(kSimpleTestFile, ElementsAreArray(&buffer[0], buffer.size())) + << "Download to disk failed, file contents did not match."; + } +} + +TEST_F(FirebaseStorageTest, TestDownloadUrl) { + SignIn(); + + const char kTestFileName[] = "TestFile-DownloadUrl.txt"; + firebase::storage::StorageReference ref = CreateFolder().Child(kTestFileName); + cleanup_files_.push_back(ref); + + LogDebug("Uploading file."); + WaitForCompletion(ref.PutBytes(&kSimpleTestFile[0], kSimpleTestFile.size()), + "PutBytes"); + + LogDebug("Getting download URL."); + firebase::Future future = ref.GetDownloadUrl(); + WaitForCompletion(future, "GetDownloadUrl"); + ASSERT_NE(future.result(), nullptr); + LogDebug("Got download URL: %s", future.result()->c_str()); + // Check for a somewhat well-formed URL, i.e. it starts with "https://" and + // has "TestFile-DownloadUrl" in the name. + EXPECT_TRUE(future.result()->find("https://") == 0) + << "Download Url doesn't start with https://"; + EXPECT_TRUE(future.result()->find(kTestFileName) != std::string::npos) + << "Download Url doesn't contain the filename " << kTestFileName; +} + +TEST_F(FirebaseStorageTest, TestDeleteFile) { + SignIn(); + + firebase::storage::StorageReference ref = + CreateFolder().Child("TestFile-Delete.txt"); + // Don't add to cleanup_files_ because we are going to delete it anyway. + + LogDebug("Uploading file."); + WaitForCompletion(ref.PutBytes(&kSimpleTestFile[0], kSimpleTestFile.size()), + "PutBytes"); + + LogDebug("Deleting file."); + WaitForCompletion(ref.Delete(), "Delete"); + + // Need a placeholder buffer. + const size_t kBufferSize = 1024; + char buffer[kBufferSize]; + // Ensure the file was deleted. + LogDebug("Ensuring file was deleted."); + firebase::Future future = ref.GetBytes(buffer, kBufferSize); + WaitForCompletion(future, "GetBytes", + firebase::storage::kErrorObjectNotFound); +} + +class StorageListener : public firebase::storage::Listener { + public: + StorageListener() + : on_paused_was_called_(false), on_progress_was_called_(false) {} + + // Tracks whether OnPaused was ever called and resumes the transfer. + void OnPaused(firebase::storage::Controller* controller) override { + on_paused_was_called_ = true; + controller->Resume(); + } + + void OnProgress(firebase::storage::Controller* controller) override { + LogDebug("Transferred %lld of %lld", controller->bytes_transferred(), + controller->total_byte_count()); + on_progress_was_called_ = true; + } + + bool on_paused_was_called() const { return on_paused_was_called_; } + bool on_progress_was_called() const { return on_progress_was_called_; } + + public: + bool on_paused_was_called_; + bool on_progress_was_called_; +}; + +// Contents of a large file, "X" will be replaced with a different character +// each line. +const char kLargeFileString[] = + "X: This is a large file with multiple lines and even some \xB1nary " + "char\xAC\ters.\n"; + +std::string CreateDataForLargeFile(size_t size_bytes) { + std::string large_test_file; + const std::string kLine = kLargeFileString; + + large_test_file.reserve(size_bytes + kLine.size()); + char replacement[2] = "a"; + while (large_test_file.size() < size_bytes) { + std::string line = kLine; + line.replace(kLine.find("X"), 1, replacement); + large_test_file += line; + replacement[0] = (replacement[0] - 'a' + 1) % 26 + 'a'; + } + large_test_file.resize(size_bytes); + return large_test_file; +} + +TEST_F(FirebaseStorageTest, TestLargeFilePauseResumeAndDownloadCancel) { + SignIn(); + + firebase::storage::StorageReference ref = + CreateFolder().Child("TestFile-LargeFile.txt"); + cleanup_files_.push_back(ref); + + const size_t kLargeFileSize = kLargeFileMegabytes * 1024 * 1024; + const std::string kLargeTestFile = CreateDataForLargeFile(kLargeFileSize); + + { + LogDebug("Uploading large file with pause/resume."); + StorageListener listener; + firebase::storage::Controller controller; + firebase::Future future = ref.PutBytes( + kLargeTestFile.c_str(), kLargeFileSize, &listener, &controller); + + // Ensure the Controller is valid now that we have associated it with an + // operation. + ASSERT_TRUE(controller.is_valid()); + + ProcessEvents(500); + // After waiting a moment for the operation to start, pause the operation + // and verify it was successfully paused. Note that pause might not take + // effect immediately, so we give it a few moments to pause before + // failing. + LogDebug("Pausing upload."); + EXPECT_TRUE(controller.Pause()) << "Upload pause"; + + // The StorageListener's OnPaused will call Resume(). + + LogDebug("Waiting for future."); + WaitForCompletion(future, "WriteLargeFile"); + LogDebug("Upload complete."); + + // Ensure the various callbacks were called. + EXPECT_TRUE(listener.on_paused_was_called()); + EXPECT_TRUE(listener.on_progress_was_called()); + + ASSERT_EQ(future.error(), firebase::storage::kErrorNone); + auto metadata = future.result(); + ASSERT_EQ(metadata->size_bytes(), kLargeFileSize) + << "Metadata reports incorrect size, file failed to upload."; + } + + // Download the file and confirm it's correct. + std::vector buffer(kLargeFileSize); + { + memset(&buffer[0], 0, kLargeFileSize); + LogDebug("Downloading large file for comparison."); + StorageListener listener; + firebase::Future future = + ref.GetBytes(&buffer[0], kLargeFileSize, &listener); + WaitForCompletion(future, "GetBytes"); + ASSERT_NE(future.result(), nullptr); + size_t file_size = *future.result(); + EXPECT_EQ(file_size, kLargeFileSize) << "Read size did not match"; + EXPECT_TRUE(memcmp(kLargeTestFile.c_str(), &buffer[0], kLargeFileSize) == 0) + << "Read large file failed, contents did not match."; + } +#if FIREBASE_PLATFORM_DESKTOP + { + // Test pausing/resuming while downloading (desktop only). + memset(&buffer[0], 0, kLargeFileSize); + LogDebug("Downloading large file with pausing/resuming."); + StorageListener listener; + firebase::storage::Controller controller; + firebase::Future future = + ref.GetBytes(&buffer[0], kLargeFileSize, &listener, &controller); + ASSERT_TRUE(controller.is_valid()); + + ProcessEvents(500); + LogDebug("Pausing download."); + EXPECT_TRUE(controller.Pause()) << "Download pause"; + + WaitForCompletion(future, "GetBytes"); + LogDebug("Download complete."); + + // Ensure the progress and pause callbacks were called. + EXPECT_TRUE(listener.on_progress_was_called()); + EXPECT_TRUE(listener.on_paused_was_called()); + + ASSERT_NE(future.result(), nullptr); + size_t file_size = *future.result(); + EXPECT_EQ(file_size, kLargeFileSize) + << "Read size with pause/resume did not match"; + EXPECT_TRUE(memcmp(kLargeTestFile.c_str(), &buffer[0], kLargeFileSize) == 0) + << "Read large file failed, contents did not match."; + } +#else + { + // Test downloading large file (mobile only), without pausing, as mobile + // does not support pause during file download, only upload. + memset(&buffer[0], 0, kLargeFileSize); + LogDebug("Downloading large file."); + StorageListener listener; + firebase::storage::Controller controller; + firebase::Future future = + ref.GetBytes(&buffer[0], kLargeFileSize, &listener, &controller); + ASSERT_TRUE(controller.is_valid()); + + WaitForCompletion(future, "GetBytes"); + LogDebug("Download complete."); + + // Ensure the progress callback was called. + EXPECT_TRUE(listener.on_progress_was_called()); + EXPECT_FALSE(listener.on_paused_was_called()); + + ASSERT_NE(future.result(), nullptr); + size_t file_size = *future.result(); + EXPECT_EQ(file_size, kLargeFileSize) << "Read size did not match"; + EXPECT_TRUE(memcmp(kLargeTestFile.c_str(), &buffer[0], kLargeFileSize) == 0) + << "Read large file failed, contents did not match."; + } +#endif // FIREBASE_PLATFORM_DESKTOP + + // Try canceling while downloading. + { + LogDebug("Downloading large file with cancellation."); + StorageListener listener; + firebase::storage::Controller controller; + firebase::Future future = + ref.GetBytes(&buffer[0], kLargeFileSize, &listener, &controller); + ASSERT_TRUE(controller.is_valid()); + ProcessEvents(500); + LogDebug("Cancelling download."); + EXPECT_TRUE(controller.Cancel()); + WaitForCompletion(future, "GetBytes", firebase::storage::kErrorCancelled); + } +} + +TEST_F(FirebaseStorageTest, TestLargeFileCancelUpload) { + SignIn(); + + firebase::storage::StorageReference ref = + CreateFolder().Child("TestFile-LargeFileCancel.txt"); + + const size_t kLargeFileSize = kLargeFileMegabytes * 1024 * 1024; + const std::string kLargeTestFile = CreateDataForLargeFile(kLargeFileSize); + { + LogDebug("Write a large file and cancel mid-way."); + StorageListener listener; + firebase::storage::Controller controller; + firebase::Future future = ref.PutBytes( + kLargeTestFile.c_str(), kLargeFileSize, &listener, &controller); + + // Ensure the Controller is valid now that we have associated it with an + // operation. + ASSERT_TRUE(controller.is_valid()); + + ProcessEvents(500); + + LogDebug("Cancelling upload."); + // Cancel the operation and verify it was successfully canceled. + EXPECT_TRUE(controller.Cancel()); + + WaitForCompletion(future, "PutBytes", firebase::storage::kErrorCancelled); + } +} + +TEST_F(FirebaseStorageTest, TestInvalidatingReferencesWhenDeletingStorage) { + SignIn(); + + // Create a file so we can get its metadata and check that it's properly + // invalidated. + firebase::storage::StorageReference ref = + CreateFolder().Child("TestFile-InvalidateReferencesDeletingStorage.txt"); + // Don't clean up, will be manually deleted. + + WaitForCompletion(ref.PutBytes(&kSimpleTestFile[0], kSimpleTestFile.size()), + "PutBytes"); + ASSERT_NE(ref.PutBytesLastResult().result(), nullptr); + firebase::storage::Metadata metadata = *ref.PutBytesLastResult().result(); + WaitForCompletion(ref.Delete(), "Delete"); + + ASSERT_TRUE(ref.is_valid()); + ASSERT_TRUE(metadata.is_valid()); + delete storage_; + storage_ = nullptr; + EXPECT_FALSE(ref.is_valid()); + EXPECT_FALSE(metadata.is_valid()); +} + +TEST_F(FirebaseStorageTest, TestInvalidatingReferencesWhenDeletingApp) { + SignIn(); + + // Create a file so we can get its metadata and check that it's properly + // invalidated. + firebase::storage::StorageReference ref = + CreateFolder().Child("TestFile-InvalidateReferencesDeletingApp.txt"); + // Don't clean up, will be manually deleted. + + WaitForCompletion(ref.PutBytes(&kSimpleTestFile[0], kSimpleTestFile.size()), + "PutBytes"); + ASSERT_NE(ref.PutBytesLastResult().result(), nullptr); + firebase::storage::Metadata metadata = *ref.PutBytesLastResult().result(); + WaitForCompletion(ref.Delete(), "Delete"); + + ASSERT_TRUE(ref.is_valid()); + ASSERT_TRUE(metadata.is_valid()); + delete app_; + app_ = nullptr; + EXPECT_FALSE(ref.is_valid()); + EXPECT_FALSE(metadata.is_valid()); +} + +} // namespace firebase_testapp_automated diff --git a/testing/sample_framework/src/android/android_app_framework.cc b/testing/sample_framework/src/android/android_app_framework.cc new file mode 100644 index 0000000000..3971f0ce60 --- /dev/null +++ b/testing/sample_framework/src/android/android_app_framework.cc @@ -0,0 +1,560 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// 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 +#include +#include +#include +#include +#include +#include +#include + +#include "app_framework.h" // NOLINT + +// This implementation is derived from http://github.com/google/fplutil + +// Number of seconds to delay after the app is finished before exiting. +// (Plus a longer delay if the app returns an error code other than 0, to give +// the user more time to see the errors.) +const int kExitDelaySeconds = 10; +const int kExitDelaySecondsIfError = 60; + +static struct android_app* g_app_state = nullptr; +static bool g_destroy_requested = false; +static bool g_started = false; +static bool g_restarted = false; +static pthread_mutex_t g_started_mutex; + +// Handle state changes from via native app glue. +static void OnAppCmd(struct android_app* app, int32_t cmd) { + g_destroy_requested |= cmd == APP_CMD_DESTROY; +} + +namespace app_framework { + +// Process events pending on the main thread. +// Returns true when the app receives an event requesting exit. +bool ProcessEvents(int msec) { + int looperId; + do { + struct android_poll_source* source = nullptr; + int events; + looperId = ALooper_pollAll(msec, nullptr, &events, + reinterpret_cast(&source)); + if (looperId >= 0 && source) { + source->process(g_app_state, source); + } + } while (looperId != ALOOPER_POLL_TIMEOUT && !g_destroy_requested && + !g_restarted); + return g_destroy_requested | g_restarted; +} + +std::string PathForResource() { + ANativeActivity* nativeActivity = g_app_state->activity; + std::string result(nativeActivity->internalDataPath); + return result + "/"; +} + +// Get the activity. +jobject GetActivity() { return g_app_state->activity->clazz; } + +// Get the window context. For Android, it's a jobject pointing to the Activity. +jobject GetWindowContext() { return g_app_state->activity->clazz; } + +// Find a class, attempting to load the class if it's not found. +jclass FindClass(JNIEnv* env, jobject activity_object, const char* class_name) { + jclass class_object = env->FindClass(class_name); + if (env->ExceptionCheck()) { + env->ExceptionClear(); + // If the class isn't found it's possible NativeActivity is being used by + // the application which means the class path is set to only load system + // classes. The following falls back to loading the class using the + // Activity before retrieving a reference to it. + jclass activity_class = env->FindClass("android/app/Activity"); + jmethodID activity_get_class_loader = env->GetMethodID( + activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); + + jobject class_loader_object = + env->CallObjectMethod(activity_object, activity_get_class_loader); + + jclass class_loader_class = env->FindClass("java/lang/ClassLoader"); + jmethodID class_loader_load_class = + env->GetMethodID(class_loader_class, "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + jstring class_name_object = env->NewStringUTF(class_name); + + class_object = static_cast(env->CallObjectMethod( + class_loader_object, class_loader_load_class, class_name_object)); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + class_object = nullptr; + } + env->DeleteLocalRef(class_name_object); + env->DeleteLocalRef(class_loader_object); + } + return class_object; +} + +// Vars that we need available for appending text to the log window: +class LoggingUtilsData { + public: + LoggingUtilsData() + : logging_utils_class_(nullptr), + logging_utils_add_log_text_(0), + logging_utils_init_log_window_(0), + logging_utils_get_did_touch_(0) {} + + ~LoggingUtilsData() { + JNIEnv* env = GetJniEnv(); + assert(env); + if (logging_utils_class_) { + env->DeleteGlobalRef(logging_utils_class_); + } + } + + void Init() { + JNIEnv* env = GetJniEnv(); + assert(env); + + jclass logging_utils_class = FindClass( + env, GetActivity(), "com/google/firebase/example/LoggingUtils"); + assert(logging_utils_class != 0); + + // Need to store as global references so it don't get moved during garbage + // collection. + logging_utils_class_ = + static_cast(env->NewGlobalRef(logging_utils_class)); + env->DeleteLocalRef(logging_utils_class); + + logging_utils_init_log_window_ = env->GetStaticMethodID( + logging_utils_class_, "initLogWindow", "(Landroid/app/Activity;)V"); + logging_utils_add_log_text_ = env->GetStaticMethodID( + logging_utils_class_, "addLogText", "(Ljava/lang/String;)V"); + logging_utils_get_did_touch_ = + env->GetStaticMethodID(logging_utils_class_, "getDidTouch", "()Z"); + logging_utils_get_log_file_ = env->GetStaticMethodID( + logging_utils_class_, "getLogFile", "()Ljava/lang/String;"); + logging_utils_start_log_file_ = + env->GetStaticMethodID(logging_utils_class_, "startLogFile", + "(Landroid/app/Activity;Ljava/lang/String;)Z"); + + env->CallStaticVoidMethod(logging_utils_class_, + logging_utils_init_log_window_, GetActivity()); + } + + void AppendText(const char* text) { + if (logging_utils_class_ == 0) return; // haven't been initted yet + JNIEnv* env = GetJniEnv(); + assert(env); + jstring text_string = env->NewStringUTF(text); + env->CallStaticVoidMethod(logging_utils_class_, logging_utils_add_log_text_, + text_string); + env->DeleteLocalRef(text_string); + } + + bool DidTouch() { + if (logging_utils_class_ == 0) return false; // haven't been initted yet + JNIEnv* env = GetJniEnv(); + assert(env); + return env->CallStaticBooleanMethod(logging_utils_class_, + logging_utils_get_did_touch_); + } + + bool IsLoggingToFile() { + if (logging_utils_class_ == 0) return false; // haven't been initted yet + JNIEnv* env = GetJniEnv(); + assert(env); + jobject file_uri = env->CallStaticObjectMethod(logging_utils_class_, + logging_utils_get_log_file_); + if (file_uri == nullptr) { + return false; + } else { + env->DeleteLocalRef(file_uri); + return true; + } + } + + bool StartLoggingToFile(const char* path) { + if (logging_utils_class_ == 0) return false; // haven't been initted yet + JNIEnv* env = GetJniEnv(); + assert(env); + jstring path_string = env->NewStringUTF(path); + jboolean b = env->CallStaticBooleanMethod(logging_utils_class_, + logging_utils_start_log_file_, + GetActivity(), path_string); + env->DeleteLocalRef(path_string); + return b ? true : false; + } + + private: + jclass logging_utils_class_; + jmethodID logging_utils_add_log_text_; + jmethodID logging_utils_init_log_window_; + jmethodID logging_utils_get_did_touch_; + jmethodID logging_utils_get_log_file_; + jmethodID logging_utils_start_log_file_; +}; + +LoggingUtilsData* g_logging_utils_data; + +// Checks if a JNI exception has happened, and if so, logs it to the console. +void CheckJNIException() { + JNIEnv* env = GetJniEnv(); + if (env->ExceptionCheck()) { + // Get the exception text. + jthrowable exception = env->ExceptionOccurred(); + env->ExceptionClear(); + + // Convert the exception to a string. + jclass object_class = env->FindClass("java/lang/Object"); + jmethodID toString = + env->GetMethodID(object_class, "toString", "()Ljava/lang/String;"); + jstring s = (jstring)env->CallObjectMethod(exception, toString); + const char* exception_text = env->GetStringUTFChars(s, nullptr); + + // Log the exception text. + __android_log_print(ANDROID_LOG_INFO, TESTAPP_NAME, + "-------------------JNI exception:"); + __android_log_print(ANDROID_LOG_INFO, TESTAPP_NAME, "%s", exception_text); + __android_log_print(ANDROID_LOG_INFO, TESTAPP_NAME, "-------------------"); + + // Also, assert fail. + assert(false); + + // In the event we didn't assert fail, clean up. + env->ReleaseStringUTFChars(s, exception_text); + env->DeleteLocalRef(s); + env->DeleteLocalRef(exception); + } +} + +// Log a message that can be viewed in "adb logcat". +void LogMessageV(bool suppress, const char* format, va_list list) { + static const int kLineBufferSize = 1024; + char buffer[kLineBufferSize + 2]; + + int string_len = vsnprintf(buffer, kLineBufferSize, format, list); + string_len = string_len < kLineBufferSize ? string_len : kLineBufferSize; + // append a linebreak to the buffer: + buffer[string_len] = '\n'; + buffer[string_len + 1] = '\0'; + + if (GetPreserveFullLog()) { + AddToFullLog(buffer); + } + if (!suppress) { + fputs(buffer, stdout); + fflush(stdout); + } +} + +void LogMessage(const char* format, ...) { + va_list list; + va_start(list, format); + LogMessageV(false, format, list); + va_end(list); +} + +static bool g_save_full_log = false; +static std::vector g_full_logs; // NOLINT + +void AddToFullLog(const char* str) { g_full_logs.push_back(std::string(str)); } + +bool GetPreserveFullLog() { return g_save_full_log; } +void SetPreserveFullLog(bool b) { g_save_full_log = b; } + +void ClearFullLog() { g_full_logs.clear(); } + +void OutputFullLog() { + for (int i = 0; i < g_full_logs.size(); ++i) { + fputs(g_full_logs[i].c_str(), stdout); + } + fflush(stdout); + ClearFullLog(); +} + +// Log a message that can be viewed in the console. +void AddToTextView(const char* str) { + app_framework::g_logging_utils_data->AppendText(str); + CheckJNIException(); +} + +// Get the JNI environment. +JNIEnv* GetJniEnv() { + JavaVM* vm = g_app_state->activity->vm; + JNIEnv* env; + jint result = vm->AttachCurrentThread(&env, nullptr); + return result == JNI_OK ? env : nullptr; +} + +bool IsLoggingToFile() { + return app_framework::g_logging_utils_data->IsLoggingToFile(); +} + +bool StartLoggingToFile(const char* path) { + return app_framework::g_logging_utils_data->StartLoggingToFile(path); +} + +// Remove all lines starting with these strings. +static const char* const filter_lines[] = {"referenceTable ", nullptr}; + +bool should_filter(const char* str) { + for (int i = 0; filter_lines[i] != nullptr; ++i) { + if (strncmp(str, filter_lines[i], strlen(filter_lines[i])) == 0) + return true; + } + return false; +} + +void* stdout_logger(void* filedes_ptr) { + int fd = reinterpret_cast(filedes_ptr)[0]; + static std::string buffer; + char bufchar; + while (int n = read(fd, &bufchar, 1)) { + if (bufchar == '\0') { + break; + } else if (bufchar == '\n') { + if (!should_filter(buffer.c_str())) { + __android_log_print(ANDROID_LOG_INFO, TESTAPP_NAME, "%s", + buffer.c_str()); + buffer = buffer + bufchar; // Add the newline + app_framework::AddToTextView(buffer.c_str()); + } + buffer.clear(); + } else { + buffer = buffer + bufchar; + } + } + JavaVM* jvm; + if (app_framework::GetJniEnv()->GetJavaVM(&jvm) == 0) { + jvm->DetachCurrentThread(); + } + return nullptr; +} + +struct FunctionData { + void* (*func)(void*); + void* data; +}; + +static void* CallFunction(void* bg_void) { + FunctionData* bg = reinterpret_cast(bg_void); + void* (*func)(void*) = bg->func; + void* data = bg->data; + // Clean up the data that was passed to us. + delete bg; + // Call the background function. + void* result = func(data); + // Then clean up the Java thread. + JavaVM* jvm; + if (app_framework::GetJniEnv()->GetJavaVM(&jvm) == 0) { + jvm->DetachCurrentThread(); + } + return result; +} + +void RunOnBackgroundThread(void* (*func)(void*), void* data) { + pthread_t thread; + // Rather than running pthread_create(func, data), we must package them into a + // struct, because the c++ thread needs to clean up the JNI thread after it + // finishes. + FunctionData* bg = new FunctionData; + bg->func = func; + bg->data = data; + pthread_create(&thread, nullptr, CallFunction, bg); + pthread_detach(thread); +} + +// Vars that we need available for reading text from the user. +class TextEntryFieldData { + public: + TextEntryFieldData() + : text_entry_field_class_(nullptr), text_entry_field_read_text_(0) {} + + ~TextEntryFieldData() { + JNIEnv* env = GetJniEnv(); + assert(env); + if (text_entry_field_class_) { + env->DeleteGlobalRef(text_entry_field_class_); + } + } + + void Init() { + JNIEnv* env = GetJniEnv(); + assert(env); + + jclass text_entry_field_class = FindClass( + env, GetActivity(), "com/google/firebase/example/TextEntryField"); + if (text_entry_field_class == 0) { + // No text entry allowed in this testapp, the Java class is not loaded. + return; + } + + // Need to store as global references so it don't get moved during garbage + // collection. + text_entry_field_class_ = + static_cast(env->NewGlobalRef(text_entry_field_class)); + env->DeleteLocalRef(text_entry_field_class); + + static const JNINativeMethod kNativeMethods[] = { + {"nativeSleep", "(I)Z", reinterpret_cast(ProcessEvents)}}; + env->RegisterNatives(text_entry_field_class_, kNativeMethods, + sizeof(kNativeMethods) / sizeof(kNativeMethods[0])); + text_entry_field_read_text_ = env->GetStaticMethodID( + text_entry_field_class_, "readText", + "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;)Ljava/lang/String;"); + } + + // Call TextEntryField.readText(), which shows a text entry dialog and spins + // until the user enters some text (or cancels). If the user cancels, returns + // an empty string. + std::string ReadText(const char* title, const char* message, + const char* placeholder) { + if (text_entry_field_class_ == 0) { + LogMessage( + "ERROR: ReadText() failed, no TextEntryField Java class is loaded."); + return ""; // Haven't been initialized. + } + JNIEnv* env = GetJniEnv(); + assert(env); + jstring title_string = env->NewStringUTF(title); + jstring message_string = env->NewStringUTF(message); + jstring placeholder_string = env->NewStringUTF(placeholder); + jobject result_string = env->CallStaticObjectMethod( + text_entry_field_class_, text_entry_field_read_text_, GetActivity(), + title_string, message_string, placeholder_string); + env->DeleteLocalRef(title_string); + env->DeleteLocalRef(message_string); + env->DeleteLocalRef(placeholder_string); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + if (result_string == nullptr) { + // Check if readText() returned null, which will be the case if an + // exception occurred or if TextEntryField returned null for some reason. + return ""; + } + const char* result_buffer = + env->GetStringUTFChars(static_cast(result_string), 0); + std::string result(result_buffer); + env->ReleaseStringUTFChars(static_cast(result_string), + result_buffer); + return result; + } + + private: + jclass text_entry_field_class_; + jmethodID text_entry_field_read_text_; +}; + +TextEntryFieldData* g_text_entry_field_data; + +// Use a Java class, TextEntryField, to prompt the user to enter some text. +// This function blocks until text was entered or the dialog was canceled. +// If the user cancels, returns an empty string. +std::string ReadTextInput(const char* title, const char* message, + const char* placeholder) { + assert(g_text_entry_field_data); + return g_text_entry_field_data->ReadText(title, message, placeholder); +} + +} // namespace app_framework + +// Execute common_main(), flush pending events and finish the activity. +extern "C" void android_main(struct android_app* state) { + // native_app_glue spawns a new thread, calling android_main() when the + // activity onStart() or onRestart() methods are called. This code handles + // the case where we're re-entering this method on a different thread by + // signalling the existing thread to exit, waiting for it to complete before + // reinitializing the application. + if (g_started) { + g_restarted = true; + // Wait for the existing thread to exit. + pthread_mutex_lock(&g_started_mutex); + pthread_mutex_unlock(&g_started_mutex); + } else { + g_started_mutex = PTHREAD_MUTEX_INITIALIZER; + } + pthread_mutex_lock(&g_started_mutex); + g_started = true; + + // Save native app glue state and setup a callback to track the state. + g_destroy_requested = false; + g_app_state = state; + g_app_state->onAppCmd = OnAppCmd; + + // Create the logging display. + app_framework::g_logging_utils_data = new app_framework::LoggingUtilsData(); + app_framework::g_logging_utils_data->Init(); + + // Create the text entry dialog. + app_framework::g_text_entry_field_data = + new app_framework::TextEntryFieldData(); + app_framework::g_text_entry_field_data->Init(); + + // Pipe stdout to AddToTextView so we get the gtest output. + int filedes[2]; + assert(pipe(filedes) != -1); + assert(dup2(filedes[1], STDOUT_FILENO) != -1); + pthread_t thread; + pthread_create(&thread, nullptr, app_framework::stdout_logger, + reinterpret_cast(filedes)); + + // Execute cross platform entry point. + // Copy the app name into a non-const array, as googletest requires that + // main() take non-const char* argv[] so it can modify the arguments. + char* argv[1]; + argv[0] = new char[strlen(TESTAPP_NAME) + 1]; + strcpy(argv[0], TESTAPP_NAME); // NOLINT + int return_value = common_main(1, argv); + delete[] argv[0]; + argv[0] = nullptr; + + app_framework::ProcessEvents(10); + + // Signal to stdout_logger to exit. + write(filedes[1], "\0", 1); + pthread_join(thread, nullptr); + close(filedes[0]); + close(filedes[1]); + // Pause a few seconds so you can see the results. If the user touches + // the screen during that time, don't exit until they choose to. + bool should_exit = false; + int exit_delay_seconds = + return_value ? kExitDelaySecondsIfError : kExitDelaySeconds; + do { + should_exit = app_framework::ProcessEvents(exit_delay_seconds * 1000); + } while (app_framework::g_logging_utils_data->DidTouch() && !should_exit); + + // Clean up logging display. + delete app_framework::g_logging_utils_data; + app_framework::g_logging_utils_data = nullptr; + + // Finish the activity. + if (!g_restarted) ANativeActivity_finish(state->activity); + + g_app_state->activity->vm->DetachCurrentThread(); + g_started = false; + g_restarted = false; + pthread_mutex_unlock(&g_started_mutex); +} diff --git a/testing/sample_framework/src/android/java/com/google/firebase/example/LoggingUtils.java b/testing/sample_framework/src/android/java/com/google/firebase/example/LoggingUtils.java new file mode 100644 index 0000000000..4ff20e8965 --- /dev/null +++ b/testing/sample_framework/src/android/java/com/google/firebase/example/LoggingUtils.java @@ -0,0 +1,169 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// 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. + +package com.google.firebase.example; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import java.io.DataOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * A utility class, encapsulating the data and methods required to log arbitrary + * text to the screen, via a non-editable TextView. + */ +public class LoggingUtils { + private static TextView textView = null; + private static ScrollView scrollView = null; + // Tracks if the log window was touched at least once since the testapp was started. + private static boolean didTouch = false; + // If a test log file is specified, this is the log file's URI... + private static Uri logFile = null; + // ...and this is the stream to write to. + private static DataOutputStream logFileStream = null; + + /** Initializes the log window with the given activity and a monospace font. */ + public static void initLogWindow(Activity activity) { + initLogWindow(activity, true); + } + + /** + * Initializes the log window with the given activity, specifying whether to use a monospaced + * font. + */ + public static void initLogWindow(Activity activity, boolean monospace) { + LinearLayout linearLayout = new LinearLayout(activity); + scrollView = new ScrollView(activity); + textView = new TextView(activity); + textView.setTag("Logger"); + if (monospace) { + textView.setTypeface(Typeface.MONOSPACE); + textView.setTextSize(10); + } + linearLayout.addView(scrollView); + scrollView.addView(textView); + Window window = activity.getWindow(); + window.takeSurface(null); + window.setContentView(linearLayout); + + // Force the TextView to stay scrolled to the bottom. + textView.addTextChangedListener( + new TextWatcher() { + @Override + public void afterTextChanged(Editable e) { + // If the user never interacted with the screen, scroll to bottom. + if (scrollView != null && !didTouch) { + new Handler(Looper.getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + scrollView.fullScroll(View.FOCUS_DOWN); + } + }); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int count, int after) {} + }); + textView.setOnTouchListener( + new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + didTouch = true; + return false; + } + }); + + Intent launchIntent = activity.getIntent(); + // Check if we are running on Firebase Test Lab, and set up a log file if we are. + if (launchIntent.getAction().equals("com.google.intent.action.TEST_LOOP")) { + startLogFile(activity, launchIntent.getData().toString()); + } + } + + /** Adds some text to the log window. */ + public static void addLogText(final String text) { + new Handler(Looper.getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + if (textView != null) { + textView.append(text); + if (logFileStream != null) { + try { + logFileStream.writeBytes(text); + } catch (IOException e) { + // It doesn't really matter if something went wrong writing to the test log. + } + } + } + } + }); + } + + /** + * Returns true if the user ever touched the log window during this run (to scroll it or + * otherwise), false if they never have. + */ + public static boolean getDidTouch() { + return didTouch; + } + + /** Start logging to a file at the given URI string. Used in TEST_LOOP mode. */ + public static boolean startLogFile(Activity activity, String logFileUri) { + logFile = Uri.parse(logFileUri); + if (logFile != null) { + try { + logFileStream = + new DataOutputStream(activity.getContentResolver().openOutputStream(logFile)); + } catch (FileNotFoundException e) { + addLogText("Failed to open log file " + logFile.getEncodedPath() + ": " + e); + return false; + } + return true; + } + return false; + } + + /** + * If the logger is logging to a file in local storage, return the URI for that file (which you + * can later pass into startLogFile in the future), otherwise return null if not logging to file. + */ + public static String getLogFile() { + if (logFile != null && logFileStream != null) { + return logFile.toString(); + } else { + return null; + } + } +} diff --git a/testing/sample_framework/src/android/java/com/google/firebase/example/TextEntryField.java b/testing/sample_framework/src/android/java/com/google/firebase/example/TextEntryField.java new file mode 100644 index 0000000000..34c59fa55d --- /dev/null +++ b/testing/sample_framework/src/android/java/com/google/firebase/example/TextEntryField.java @@ -0,0 +1,106 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// 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. + +package com.google.firebase.example; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.widget.EditText; + +/** + * A utility class, with a method to prompt the user to enter a line of text, and a native method to + * sleep for a given number of milliseconds. + */ +public class TextEntryField { + private static Object lock = new Object(); + private static String resultText = null; + + /** + * Prompt the user with a text field, blocking until the user fills it out, then returns the text + * they entered. If the user cancels, returns an empty string. + */ + public static String readText( + final Activity activity, final String title, final String message, final String placeholder) { + resultText = null; + // Show the alert dialog on the main thread. + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(activity); + alertBuilder.setTitle(title); + alertBuilder.setMessage(message); + + // Set up and add the text field. + final EditText textField = new EditText(activity); + textField.setHint(placeholder); + alertBuilder.setView(textField); + + alertBuilder.setPositiveButton( + "OK", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + synchronized (lock) { + resultText = textField.getText().toString(); + } + } + }); + + alertBuilder.setNegativeButton( + "Cancel", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + synchronized (lock) { + resultText = ""; + } + } + }); + + alertBuilder.setOnCancelListener( + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + synchronized (lock) { + resultText = ""; + } + } + }); + alertBuilder.show(); + } + }); + + // In our original thread, wait for the dialog to finish, then return its result. + while (true) { + // Pause a second, waiting for the user to enter text. + if (nativeSleep(1000)) { + // If this returns true, an exit was requested. + return ""; + } + synchronized (lock) { + if (resultText != null) { + // resultText will be set to non-null when a dialog button is clicked, or the dialog + // is canceled. + String result = resultText; + resultText = null; // Consume the result. + return result; + } + } + } + } + + private static native boolean nativeSleep(int milliseconds); +} diff --git a/testing/sample_framework/src/app_framework.cc b/testing/sample_framework/src/app_framework.cc new file mode 100644 index 0000000000..bed0d01526 --- /dev/null +++ b/testing/sample_framework/src/app_framework.cc @@ -0,0 +1,96 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 "app_framework.h" // NOLINT + +#include +#include + +#include +#include +#include +#include +#include + +namespace app_framework { + +// Base logging methods, implemented by platform-specific files. +void LogMessage(const char* format, ...); +void LogMessageV(bool filtered, const char* format, va_list list); + +enum LogLevel g_log_level = kInfo; + +void SetLogLevel(LogLevel log_level) { g_log_level = log_level; } + +LogLevel GetLogLevel() { return g_log_level; } + +void LogDebug(const char* format, ...) { + va_list list; + va_start(list, format); + std::string format_str("DEBUG: "); + format_str += format; + app_framework::LogMessageV(g_log_level > kDebug, format_str.c_str(), list); + va_end(list); +} + +void LogInfo(const char* format, ...) { + va_list list; + va_start(list, format); + std::string format_str("INFO: "); + format_str += format; + app_framework::LogMessageV(g_log_level > kInfo, format_str.c_str(), list); + va_end(list); +} + +void LogWarning(const char* format, ...) { + va_list list; + va_start(list, format); + std::string format_str("WARNING: "); + format_str += format; + app_framework::LogMessageV(g_log_level > kWarning, format_str.c_str(), list); + va_end(list); +} + +void LogError(const char* format, ...) { + va_list list; + va_start(list, format); + std::string format_str("ERROR: "); + format_str += format; + app_framework::LogMessageV(g_log_level > kError, format_str.c_str(), list); + va_end(list); +} + +#if !defined(_WIN32) // Windows requires its own version of time-handling code. +int64_t GetCurrentTimeInMicroseconds() { + struct timeval now; + gettimeofday(&now, nullptr); + return now.tv_sec * 1000000LL + now.tv_usec; +} +#endif // !defined(_WIN32) + +#if defined(__ANDROID__) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +void ChangeToFileDirectory(const char*) {} +#endif // defined(__ANDROID__) || (defined(TARGET_OS_IPHONE) && + // TARGET_OS_IPHONE) + +#if defined(_WIN32) +#define stat _stat +#endif // defined(_WIN32) + +bool FileExists(const char* file_path) { + struct stat s; + return stat(file_path, &s) == 0; +} + +} // namespace app_framework diff --git a/testing/sample_framework/src/app_framework.h b/testing/sample_framework/src/app_framework.h new file mode 100644 index 0000000000..580b3bd939 --- /dev/null +++ b/testing/sample_framework/src/app_framework.h @@ -0,0 +1,131 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// 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 APP_FRAMEWORK_H_ // NOLINT +#define APP_FRAMEWORK_H_ // NOLINT + +#include +#include + +#if defined(__APPLE__) +#include +#endif // defined(__APPLE__) + +#if !defined(_WIN32) +#include +#endif +#if defined(__ANDROID__) +#include +#include +#elif defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +extern "C" { +#include +} // extern "C" +#endif // platforms + +// Defined using -DTESTAPP_NAME=some_app_name when compiling this +// file. +#ifndef TESTAPP_NAME +#define TESTAPP_NAME "android_main" +#endif // TESTAPP_NAME + +extern "C" int common_main(int argc, char* argv[]); + +namespace app_framework { + +// Platform-independent logging methods. +enum LogLevel { kDebug = 0, kInfo, kWarning, kError }; +void SetLogLevel(LogLevel log_level); +LogLevel GetLogLevel(); +void LogError(const char* format, ...); +void LogWarning(const char* format, ...); +void LogInfo(const char* format, ...); +void LogDebug(const char* format, ...); + +// Set this to true to have all log messages saved regardless of loglevel; you +// can output them later via OutputFullLog or clear them via ClearFullLog. +void SetPreserveFullLog(bool b); +// Get the value previously set by SetPreserveFullLog. +bool GetPreserveFullLog(); + +// Add a line of text to the "full log" to be output via OutputFullLog. +void AddToFullLog(const char* str); + +// Clear the logs that were saved. +void ClearFullLog(); + +// Output the full saved logs (if you SetPreserveFullLog(true) earlier). +void OutputFullLog(); + +// Platform-independent method to flush pending events for the main thread. +// Returns true when an event requesting program-exit is received. +bool ProcessEvents(int msec); + +// Returns a path to a writable directory for the given platform. +std::string PathForResource(); + +// Returns the number of microseconds since the epoch. +int64_t GetCurrentTimeInMicroseconds(); + +// On desktop, change the current working directory to the directory +// containing the specified file. On mobile, this is a no-op. +void ChangeToFileDirectory(const char* file_path); + +// Return whether the file exists. +bool FileExists(const char* file_path); + +// WindowContext represents the handle to the parent window. Its type +// (and usage) vary based on the OS. +#if defined(__ANDROID__) +typedef jobject WindowContext; // A jobject to the Java Activity. +#elif defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +typedef id WindowContext; // A pointer to an iOS UIView. +#else +typedef void* WindowContext; // A void* for any other environments. +#endif + +#if defined(__ANDROID__) +// Get the JNI environment. +JNIEnv* GetJniEnv(); +// Get the activity. +jobject GetActivity(); +// Find a class, attempting to load the class if it's not found. +jclass FindClass(JNIEnv* env, jobject activity_object, const char* class_name); +#endif // defined(__ANDROID__) + +// Returns true if the logger is currently logging to a file. +bool IsLoggingToFile(); + +// Start logging to the given file. You only need to do this if the app has been +// restarted since it was initially run in test loop mode. +bool StartLoggingToFile(const char* path); + +// Returns a variable that describes the window context for the app. On Android +// this will be a jobject pointing to the Activity. On iOS, it's an id pointing +// to the root view of the view controller. +WindowContext GetWindowContext(); + +// Run the given function on a detached background thread. +void RunOnBackgroundThread(void* (*func)(void* data), void* data); + +// Prompt the user with a dialog box to enter a line of text, blocking +// until the user enters the text or the dialog box is canceled. +// Returns the text that was entered, or an empty string if the user +// canceled. +std::string ReadTextInput(const char* title, const char* message, + const char* placeholder); + +} // namespace app_framework + +#endif // APP_FRAMEWORK_H_ // NOLINT diff --git a/testing/sample_framework/src/desktop/desktop_app_framework.cc b/testing/sample_framework/src/desktop/desktop_app_framework.cc new file mode 100644 index 0000000000..115ad2bce2 --- /dev/null +++ b/testing/sample_framework/src/desktop/desktop_app_framework.cc @@ -0,0 +1,215 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// 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 +#include +#include +#include // NOLINT +#include + +#ifdef _WIN32 +#include +#define chdir _chdir +#else +#include +#include +#include +#endif // _WIN32 + +#ifdef _WIN32 +#include +#endif // _WIN32 + +#include "app_framework.h" // NOLINT + +static bool quit = false; + +#ifdef _WIN32 +static BOOL WINAPI SignalHandler(DWORD event) { + if (!(event == CTRL_C_EVENT || event == CTRL_BREAK_EVENT)) { + return FALSE; + } + quit = true; + return TRUE; +} +#else +static void SignalHandler(int /* ignored */) { quit = true; } +#endif // _WIN32 + +namespace app_framework { + +bool ProcessEvents(int msec) { +#ifdef _WIN32 + Sleep(msec); +#else + usleep(msec * 1000); +#endif // _WIN32 + return quit; +} + +std::string PathForResource() { +#if defined(_WIN32) + // On Windows we should hvae TEST_TMPDIR or TEMP or TMP set. + char buf[MAX_PATH + 1]; + if (GetEnvironmentVariable("TEST_TMPDIR", buf, sizeof(buf)) || + GetEnvironmentVariable("TEMP", buf, sizeof(buf)) || + GetEnvironmentVariable("TMP", buf, sizeof(buf))) { + std::string path(buf); + // Add trailing slash. + if (path[path.size() - 1] != '\\') path += '\\'; + return path; + } +#else + // Linux and OS X should either have the TEST_TMPDIR environment variable set + // or use /tmp. + if (const char* value = getenv("TEST_TMPDIR")) { + std::string path(value); + // Add trailing slash. + if (path[path.size() - 1] != '/') path += '/'; + return path; + } + struct stat s; + if (stat("/tmp", &s) == 0) { + if (s.st_mode & S_IFDIR) { + return std::string("/tmp/"); + } + } +#endif // defined(_WIN32) + // If nothing else, use the current directory. + return std::string(); +} +void LogMessageV(bool suppress, const char* format, va_list list) { + // Save the log to the g_full_logs list regardless of whether it should be + // suppressed. + static const int kLineBufferSize = 1024; + char buffer[kLineBufferSize + 2]; + int string_len = vsnprintf(buffer, kLineBufferSize, format, list); + string_len = string_len < kLineBufferSize ? string_len : kLineBufferSize; + // Append a linebreak to the buffer. + buffer[string_len] = '\n'; + buffer[string_len + 1] = '\0'; + if (GetPreserveFullLog()) { + AddToFullLog(buffer); + } + if (!suppress) { + fputs(buffer, stdout); + fflush(stdout); + } +} + +void LogMessage(const char* format, ...) { + va_list list; + va_start(list, format); + LogMessageV(false, format, list); + va_end(list); +} + +static bool g_save_full_log = false; +static std::vector g_full_logs; // NOLINT + +void AddToFullLog(const char* str) { g_full_logs.push_back(std::string(str)); } + +bool GetPreserveFullLog() { return g_save_full_log; } +void SetPreserveFullLog(bool b) { g_save_full_log = b; } + +void ClearFullLog() { g_full_logs.clear(); } + +void OutputFullLog() { + for (int i = 0; i < g_full_logs.size(); ++i) { + fputs(g_full_logs[i].c_str(), stdout); + } + fflush(stdout); + ClearFullLog(); +} + +WindowContext GetWindowContext() { return nullptr; } + +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char* file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) { + LogDebug("chdir %s", directory.c_str()); + chdir(directory.c_str()); + } + } +} + +#if defined(_WIN32) // The other platforms are implemented in app_framework.cc. +// Returns the number of microseconds since the epoch. +int64_t GetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10LL; +} +#endif // defined(_WIN32) + +void RunOnBackgroundThread(void* (*func)(void*), void* data) { + // On desktop, use std::thread as Windows doesn't support pthreads. + std::thread thread(func, data); + thread.detach(); +} + +std::string ReadTextInput(const char* title, const char* message, + const char* placeholder) { + if (title && *title) { + int len = strlen(title); + printf("\n"); + for (int i = 0; i < len; ++i) { + printf("="); + } + printf("\n%s\n", title); + for (int i = 0; i < len; ++i) { + printf("="); + } + } + printf("\n%s", message); + if (placeholder && *placeholder) { + printf(" [%s]", placeholder); + } + printf(": "); + fflush(stdout); + std::string input_line; + std::getline(std::cin, input_line); + return input_line.empty() ? std::string(placeholder) : input_line; +} + +bool IsLoggingToFile() { return false; } + +} // namespace app_framework + +int main(int argc, char* argv[]) { +#ifdef _WIN32 + SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); +#else + signal(SIGINT, SignalHandler); +#endif // _WIN32 + return common_main(argc, argv); +} diff --git a/testing/sample_framework/src/ios/ios_app_framework.mm b/testing/sample_framework/src/ios/ios_app_framework.mm new file mode 100755 index 0000000000..2710c1f544 --- /dev/null +++ b/testing/sample_framework/src/ios/ios_app_framework.mm @@ -0,0 +1,346 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "app_framework.h" + +@interface AppDelegate : UIResponder + +@property(nonatomic, strong) UIWindow *window; + +@end + +@interface FTAViewController : UIViewController + +@property(atomic, strong) NSString *textEntryResult; + +@end + +static NSString * const kGameLoopUrlPrefix= @"firebase-game-loop"; +static NSString * const kGameLoopCompleteUrlScheme= @"firebase-game-loop-complete://"; +static const float kGameLoopSecondsToPauseBeforeQuitting = 5.0f; + +// Test Loop on iOS doesn't provide the app under test a path to save logs to, so set it here. +#define GAMELOOP_DEFAULT_LOG_FILE "Results1.json" + +static int g_exit_status = 0; +static bool g_shutdown = false; +static NSCondition *g_shutdown_complete; +static NSCondition *g_shutdown_signal; +static UITextView *g_text_view; +static UIView *g_parent_view; +static FTAViewController *g_view_controller; +static bool g_gameloop_launch = false; +static NSURL *g_results_url; +static NSString *g_file_name; +static NSString *g_file_url_path; + +@implementation FTAViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + g_parent_view = self.view; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // Copy the app name into a non-const array, as googletest requires that + // main() take non-const char* argv[] so it can modify the arguments. + char *argv[1]; + argv[0] = new char[strlen(TESTAPP_NAME) + 1]; + strcpy(argv[0], TESTAPP_NAME); // NOLINT + [g_shutdown_signal lock]; + g_exit_status = common_main(1, argv); + [g_shutdown_complete signal]; + delete[] argv[0]; + argv[0] = nullptr; + [NSThread sleepForTimeInterval:kGameLoopSecondsToPauseBeforeQuitting]; + [UIApplication.sharedApplication openURL:[NSURL URLWithString:kGameLoopCompleteUrlScheme] + options:[NSDictionary dictionary] + completionHandler:nil]; + + }); +} + +@end +namespace app_framework { + +bool ProcessEvents(int msec) { + [g_shutdown_signal + waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:static_cast(msec) / 1000.0f]]; + return g_shutdown; +} + +std::string PathForResource() { + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = paths.firstObject; + // Force a trailing slash by removing any that exists, then appending another. + return std::string( + [[documentsDirectory stringByStandardizingPath] stringByAppendingString:@"/"].UTF8String); +} + +WindowContext GetWindowContext() { + return g_parent_view; +} + +// Log a message that can be viewed in the console. +void LogMessageV(bool suppress, const char *format, va_list list) { + NSString *formatString = @(format); + + NSString *message = [[NSString alloc] initWithFormat:formatString arguments:list]; + message = [message stringByAppendingString:@"\n"]; + + if (GetPreserveFullLog()) { + AddToFullLog(message.UTF8String); + } + if (!suppress) { + fputs(message.UTF8String, stdout); + fflush(stdout); + } +} + +void LogMessage(const char *format, ...) { + va_list list; + va_start(list, format); + LogMessageV(false, format, list); + va_end(list); +} + +static bool g_save_full_log = false; +static std::vector g_full_logs; // NOLINT + +void AddToFullLog(const char* str) { g_full_logs.push_back(std::string(str)); } + +bool GetPreserveFullLog() { return g_save_full_log; } +void SetPreserveFullLog(bool b) { g_save_full_log = b; } + +void ClearFullLog() { g_full_logs.clear(); } + +void OutputFullLog() { + for (int i = 0; i < g_full_logs.size(); ++i) { + fputs(g_full_logs[i].c_str(), stdout); + } + fflush(stdout); + ClearFullLog(); +} + +// Log a message that can be viewed in the console. +void AddToTextView(const char *str) { + NSString *message = @(str); + + dispatch_async(dispatch_get_main_queue(), ^{ + g_text_view.text = [g_text_view.text stringByAppendingString:message]; + NSRange range = NSMakeRange(g_text_view.text.length, 0); + [g_text_view scrollRangeToVisible:range]; + }); + if (g_gameloop_launch) { + NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding]; + if ([NSFileManager.defaultManager fileExistsAtPath:g_file_url_path]) { + NSFileHandle *fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:g_file_url_path]; + [fileHandler seekToEndOfFile]; + [fileHandler writeData:data]; + [fileHandler closeFile]; + } else { + NSLog(@"Write to file %@", g_file_url_path); + [data writeToFile:g_file_url_path atomically:YES]; + } + } + +} + +// Remove all lines starting with these strings. +static const char *const filter_lines[] = {nullptr}; + +bool should_filter(const char *str) { + for (int i = 0; filter_lines[i] != nullptr; ++i) { + if (strncmp(str, filter_lines[i], strlen(filter_lines[i])) == 0) return true; + } + return false; +} + +void *stdout_logger(void *filedes_ptr) { + int fd = reinterpret_cast(filedes_ptr)[0]; + std::string buffer; + char bufchar; + while (int n = read(fd, &bufchar, 1)) { + if (bufchar == '\0') { + break; + } else if (bufchar == '\n') { + if (!should_filter(buffer.c_str())) { + NSLog(@"%s", buffer.c_str()); + buffer = buffer + bufchar; // Add the newline + app_framework::AddToTextView(buffer.c_str()); + } + buffer.clear(); + } else { + buffer = buffer + bufchar; + } + } + return nullptr; +} + +void RunOnBackgroundThread(void* (*func)(void*), void* data) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + func(data); + }); +} + +// Create an alert dialog via UIAlertController, and prompt the user to enter a line of text. +// This function spins until the text has been entered (or the alert dialog was canceled). +// If the user cancels, returns an empty string. +std::string ReadTextInput(const char *title, const char *message, const char *placeholder) { + assert(g_view_controller); + // This should only be called from a background thread, as it blocks, which will mess up the main + // thread. + assert(![NSThread isMainThread]); + + g_view_controller.textEntryResult = nil; + + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertController *alertController = + [UIAlertController alertControllerWithTitle:@(title) + message:@(message) + preferredStyle:UIAlertControllerStyleAlert]; + [alertController addTextFieldWithConfigurationHandler:^(UITextField *_Nonnull textField) { + textField.placeholder = @(placeholder); + }]; + UIAlertAction *confirmAction = [UIAlertAction + actionWithTitle:@"OK" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + g_view_controller.textEntryResult = alertController.textFields.firstObject.text; + }]; + [alertController addAction:confirmAction]; + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *_Nonnull action) { + g_view_controller.textEntryResult = @""; + }]; + [alertController addAction:cancelAction]; + [g_view_controller presentViewController:alertController animated:YES completion:nil]; + }); + + while (true) { + // Pause a second, waiting for the user to enter text. + if (ProcessEvents(1000)) { + // If this returns true, an exit was requested. + return ""; + } + if (g_view_controller.textEntryResult != nil) { + // textEntryResult will be set to non-nil when a dialog button is clicked. + std::string result = g_view_controller.textEntryResult.UTF8String; + g_view_controller.textEntryResult = nil; // Consume the result. + return result; + } + } +} + +bool IsLoggingToFile() { return g_file_url_path; } + +bool StartLoggingToFile(const char *file_path) { + NSURL *home_url = [NSURL fileURLWithPath:NSHomeDirectory()]; + g_results_url = [home_url URLByAppendingPathComponent:@"/Documents/GameLoopResults"]; + g_file_name = @(file_path); + g_file_url_path = [g_results_url URLByAppendingPathComponent:g_file_name].path; + NSError *error; + if (![NSFileManager.defaultManager fileExistsAtPath:[g_results_url path]]) { + if (![NSFileManager.defaultManager createDirectoryAtPath:g_results_url.path + withIntermediateDirectories:true + attributes:nil + error:&error]) { + app_framework::LogError("Couldn't create directory %s: %s", g_results_url.path, + error.description.UTF8String); + g_file_url_path = nil; + return false; + } + } + return true; +} + +} // namespace app_framework + +int main(int argc, char* argv[]) { + // Pipe stdout to call LogToTextView so we can see the gtest output. + int filedes[2]; + assert(pipe(filedes) != -1); + assert(dup2(filedes[1], STDOUT_FILENO) != -1); + pthread_t thread; + pthread_create(&thread, nullptr, app_framework::stdout_logger, reinterpret_cast(filedes)); + @autoreleasepool { + UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } + // Signal to stdout_logger to exit. + write(filedes[1], "\0", 1); + pthread_join(thread, nullptr); + close(filedes[0]); + close(filedes[1]); + + NSLog(@"Application Exit"); + return g_exit_status; +} + +@implementation AppDelegate +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + if ([url.scheme isEqual:kGameLoopUrlPrefix]) { + g_gameloop_launch = true; + app_framework::StartLoggingToFile(GAMELOOP_DEFAULT_LOG_FILE); + return YES; + } + NSLog(@"The testapp will not log to files since it is not launched by URL %@", + kGameLoopUrlPrefix); + return NO; +} + +- (BOOL)application:(UIApplication*)application + didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + g_shutdown_complete = [[NSCondition alloc] init]; + g_shutdown_signal = [[NSCondition alloc] init]; + [g_shutdown_complete lock]; + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + g_view_controller = [[FTAViewController alloc] init]; + self.window.rootViewController = g_view_controller; + [self.window makeKeyAndVisible]; + + g_text_view = [[UITextView alloc] initWithFrame:g_view_controller.view.bounds]; + + g_text_view.accessibilityIdentifier = @"Logger"; + g_text_view.editable = NO; + g_text_view.scrollEnabled = YES; + g_text_view.userInteractionEnabled = YES; + g_text_view.font = [UIFont fontWithName:@"Courier" size:10]; + [g_view_controller.view addSubview:g_text_view]; + + return YES; +} + +- (void)applicationWillTerminate:(UIApplication *)application { + g_shutdown = true; + [g_shutdown_signal signal]; + [g_shutdown_complete wait]; +} +@end diff --git a/testing/test_framework/download_googletest.py b/testing/test_framework/download_googletest.py new file mode 100755 index 0000000000..9f3bd9883c --- /dev/null +++ b/testing/test_framework/download_googletest.py @@ -0,0 +1,79 @@ +#!/usr/bin/python + +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Download GoogleTest from GitHub into a subdirectory.""" + +# pylint: disable=superfluous-parens,g-import-not-at-top + +import io +import os +import shutil +import tempfile +import zipfile + +# Import urllib in a way that is compatible with Python 2 and Python 3. +try: + import urllib.request + compatible_urlopen = urllib.request.urlopen +except ImportError: + import urllib2 + compatible_urlopen = urllib2.urlopen + +# Run from inside the script directory +SRC_DIR = os.path.relpath(os.path.dirname(__file__)) +TAG = os.path.basename(__file__) + +GOOGLETEST_ZIP = 'https://github.com/google/googletest/archive/master.zip' +# Top-level directory inside the zip file to ignore. +GOOGLETEST_DIR = os.path.join('googletest-master') + +# The GoogleTest code is copied into this subdirectory. +# This structure matches where the files are placed by CMake. +DESTINATION_DIR = os.path.join(SRC_DIR, 'external/googletest/src') + +CHECK_EXISTS = os.path.join( + SRC_DIR, 'external/googletest/src/googletest/src/gtest-all.cc') + +# Don't download it again if it already exists. +if os.path.exists(CHECK_EXISTS): + print('%s: GoogleTest already downloaded, skipping.' % (TAG)) + exit(0) + +# Download the zipfile into memory, extract into /tmp, then move into the +# current directory. + +try: + # Download to a temporary directory. + zip_extract_path = tempfile.mkdtemp(suffix='googletestdownload') + print('%s: Downloading GoogleTest from %s' % (TAG, GOOGLETEST_ZIP)) + zip_download = compatible_urlopen(GOOGLETEST_ZIP) + zip_file = io.BytesIO(zip_download.read()) + print('%s: Extracting GoogleTest...' % (TAG)) + zip_ref = zipfile.ZipFile(zip_file, mode='r') + zip_ref.extractall(zip_extract_path) + if os.path.exists(DESTINATION_DIR): + shutil.rmtree(DESTINATION_DIR) + shutil.move(os.path.join(zip_extract_path, GOOGLETEST_DIR), DESTINATION_DIR) + print('%s: Finished.' % (TAG)) +except Exception as e: + raise +finally: + # Clean up the temp directory if we created one. + if os.path.exists(zip_extract_path): + shutil.rmtree(zip_extract_path) + +if not os.path.exists(CHECK_EXISTS): + print('%s: Failed to download GoogleTest to %s' % (TAG, DESTINATION_DIR)) + exit(1) diff --git a/testing/test_framework/src/android/android_firebase_test_framework.cc b/testing/test_framework/src/android/android_firebase_test_framework.cc new file mode 100644 index 0000000000..12c092f2d9 --- /dev/null +++ b/testing/test_framework/src/android/android_firebase_test_framework.cc @@ -0,0 +1,225 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 "firebase_test_framework.h" // NOLINT + +namespace firebase_test_framework { + +using app_framework::LogDebug; +using app_framework::LogError; + +// Blocking HTTP request helper function. +static bool SendHttpRequest(const char* url, + const std::map& headers, + const std::string* post_body, int* response_code, + std::string* response_str) { + JNIEnv* env = app_framework::GetJniEnv(); + jobject activity = app_framework::GetActivity(); + jclass simple_http_request_class = app_framework::FindClass( + env, activity, "com/google/firebase/example/SimpleHttpRequest"); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return false; + } + jmethodID constructor = env->GetMethodID(simple_http_request_class, "", + "(Ljava/lang/String;)V"); + jmethodID set_post_data = + env->GetMethodID(simple_http_request_class, "setPostData", "([B)V"); + jmethodID add_header = + env->GetMethodID(simple_http_request_class, "addHeader", + "(Ljava/lang/String;Ljava/lang/String;)V"); + jmethodID perform = env->GetMethodID(simple_http_request_class, "perform", + "()Ljava/lang/String;"); + jmethodID get_response_code = + env->GetMethodID(simple_http_request_class, "getResponseCode", "()I"); + + jstring url_jstring = env->NewStringUTF(url); + // http_request = new SimpleHttpRequestClass(url); + jobject http_request = + env->NewObject(simple_http_request_class, constructor, url_jstring); + env->DeleteLocalRef(url_jstring); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + if (http_request) env->DeleteLocalRef(http_request); + return false; + } + // for (header : headers) { + // http_request.addHeader(header.key, header.value); + // } + for (auto i = headers.begin(); i != headers.end(); ++i) { + jstring key_jstring = env->NewStringUTF(i->first.c_str()); + jstring value_jstring = env->NewStringUTF(i->second.c_str()); + env->CallVoidMethod(http_request, add_header, key_jstring, value_jstring); + env->DeleteLocalRef(key_jstring); + env->DeleteLocalRef(value_jstring); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + if (http_request) env->DeleteLocalRef(http_request); + return false; + } + } + if (post_body != nullptr) { + // http_request.setPostBody(post_body); + jbyteArray post_body_array = env->NewByteArray(post_body->length()); + env->SetByteArrayRegion(post_body_array, 0, post_body->length(), + reinterpret_cast(post_body->c_str())); + env->CallVoidMethod(http_request, set_post_data, post_body_array); + env->DeleteLocalRef(post_body_array); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + if (http_request) env->DeleteLocalRef(http_request); + return false; + } + } + // String response = http_request.perform(); + jobject response = env->CallObjectMethod(http_request, perform); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + jstring response_jstring = static_cast(response); + // int response_code = http_request.getResponseCode(); + jint response_code_jint = env->CallIntMethod(http_request, get_response_code); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + LogDebug("HTTP status code %d", response_code_jint); + if (response_code) *response_code = response_code_jint; + + env->DeleteLocalRef(http_request); + if (response_jstring == nullptr) { + return false; + } + const char* response_text = env->GetStringUTFChars(response_jstring, nullptr); + LogDebug("Got response: %s", response_text); + if (response_str) *response_str = response_text; + env->ReleaseStringUTFChars(response_jstring, response_text); + env->DeleteLocalRef(response); + return true; +} + +// Blocking HTTP request helper function, for testing only. +bool FirebaseTest::SendHttpGetRequest( + const char* url, const std::map& headers, + int* response_code, std::string* response_str) { + return SendHttpRequest(url, headers, nullptr, response_code, response_str); +} + +bool FirebaseTest::SendHttpPostRequest( + const char* url, const std::map& headers, + const std::string& post_body, int* response_code, + std::string* response_str) { + return SendHttpRequest(url, headers, &post_body, response_code, response_str); +} + +bool FirebaseTest::OpenUrlInBrowser(const char* url) { + JNIEnv* env = app_framework::GetJniEnv(); + jobject activity = app_framework::GetActivity(); + jclass simple_http_request_class = app_framework::FindClass( + env, activity, "com/google/firebase/example/SimpleHttpRequest"); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return false; + } + jmethodID open_url = + env->GetStaticMethodID(simple_http_request_class, "openUrlInBrowser", + "(Ljava/lang/String;Landroid/app/Activity;)V"); + jstring url_jstring = env->NewStringUTF(url); + env->CallStaticVoidMethod(simple_http_request_class, open_url, url_jstring, + activity); + env->DeleteLocalRef(url_jstring); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return false; + } + return true; +} + +bool FirebaseTest::SetPersistentString(const char* key, const char* value) { + if (key == nullptr) { + LogError("SetPersistentString: null key is not allowed."); + return false; + } + JNIEnv* env = app_framework::GetJniEnv(); + jobject activity = app_framework::GetActivity(); + jclass simple_persistent_storage_class = app_framework::FindClass( + env, activity, "com/google/firebase/example/SimplePersistentStorage"); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return false; + } + jmethodID set_string = env->GetStaticMethodID( + simple_persistent_storage_class, "setString", + "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)V"); + jstring key_jstring = env->NewStringUTF(key); + jstring value_jstring = value ? env->NewStringUTF(value) : nullptr; + env->CallStaticVoidMethod(simple_persistent_storage_class, set_string, + activity, key_jstring, value_jstring); + env->DeleteLocalRef(key_jstring); + if (value_jstring) { + env->DeleteLocalRef(value_jstring); + } + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return false; + } + return true; +} + +bool FirebaseTest::GetPersistentString(const char* key, + std::string* value_out) { + JNIEnv* env = app_framework::GetJniEnv(); + jobject activity = app_framework::GetActivity(); + jclass simple_persistent_storage_class = app_framework::FindClass( + env, activity, "com/google/firebase/example/SimplePersistentStorage"); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return false; + } + jmethodID get_string = env->GetStaticMethodID( + simple_persistent_storage_class, "getString", + "(Landroid/app/Activity;Ljava/lang/String;)Ljava/lang/String;"); + jstring key_jstring = env->NewStringUTF(key); + jstring value_jstring = static_cast(env->CallStaticObjectMethod( + simple_persistent_storage_class, get_string, activity, key_jstring)); + env->DeleteLocalRef(key_jstring); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + if (value_jstring) { + env->DeleteLocalRef(value_jstring); + } + return false; + } + if (value_jstring == nullptr) { + return false; + } + const char* value_text = env->GetStringUTFChars(value_jstring, nullptr); + if (value_out) *value_out = std::string(value_text); + env->ReleaseStringUTFChars(value_jstring, value_text); + env->DeleteLocalRef(value_jstring); + return true; +} + +} // namespace firebase_test_framework diff --git a/testing/test_framework/src/android/java/com/google/firebase/example/SimpleHttpRequest.java b/testing/test_framework/src/android/java/com/google/firebase/example/SimpleHttpRequest.java new file mode 100644 index 0000000000..d56a2d6763 --- /dev/null +++ b/testing/test_framework/src/android/java/com/google/firebase/example/SimpleHttpRequest.java @@ -0,0 +1,118 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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. + +package com.google.firebase.example; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +/** + * A simple client for performing synchronous HTTP/HTTPS requests, used for testing purposes only. + */ +public final class SimpleHttpRequest { + private URL url; + private final HashMap headers; + private byte[] postData; + private int responseCode; + /** Create a new SimpleHttpRequest with a default URL. */ + public SimpleHttpRequest(String urlString) throws MalformedURLException { + this.headers = new HashMap<>(); + this.postData = null; + setUrl(urlString); + } + /** Set the URL to the given string, or null if it can't be parsed. */ + public void setUrl(String urlString) throws MalformedURLException { + this.url = new URL(urlString); + } + /** Get the previously-set URL. */ + public URL getUrl() { + return this.url; + } + /** Set the HTTP POST body, and set this request to a POST request. */ + public void setPostData(byte[] postData) { + this.postData = postData; + } + /** Clear out the HTTP POST body, setting this request back to a GET request. */ + public void clearPostData() { + this.postData = null; + } + /** Add a header key-value pair. */ + public void addHeader(String key, String value) { + this.headers.put(key, value); + } + /** Clear previously-set headers. */ + public void clearHeaders() { + this.headers.clear(); + } + + /** Get the response code returned by the server, after perform() is finished. */ + public int getResponseCode() { + return this.responseCode; + } + + /** + * Perform a HTTP request to the given URL, with the given headers. If postData is non-null, use a + * POST request, else use a GET request. This method blocks until getting a response. + */ + public String perform() throws IOException { + if (this.url == null) { + return null; + } + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod(postData != null ? "POST" : "GET"); + for (Map.Entry entry : this.headers.entrySet()) { + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } + if (this.postData != null) { + connection.setDoOutput(true); + connection.setFixedLengthStreamingMode(postData.length); + connection.getOutputStream().write(postData); + } + responseCode = connection.getResponseCode(); + StringBuilder result = new StringBuilder(); + BufferedReader inputStream = + new BufferedReader(new InputStreamReader(connection.getInputStream())); + String line; + while ((line = inputStream.readLine()) != null) { + result.append(line); + } + connection.disconnect(); + return result.toString(); + } + + /** A one-off helper method to simply open a URL in a browser window. */ + public static void openUrlInBrowser(String urlString, Activity activity) { + if (urlString.startsWith("data:")) { + // Use makeMainSelectorActivity to handle data: URLs. + activity.startActivity( + Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER) + .setData(Uri.parse(urlString))); + } else { + // Otherwise use the default intent handler for the URL. + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(urlString)); + activity.startActivity(intent); + } + } +} diff --git a/testing/test_framework/src/android/java/com/google/firebase/example/SimplePersistentStorage.java b/testing/test_framework/src/android/java/com/google/firebase/example/SimplePersistentStorage.java new file mode 100644 index 0000000000..62d596c678 --- /dev/null +++ b/testing/test_framework/src/android/java/com/google/firebase/example/SimplePersistentStorage.java @@ -0,0 +1,45 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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. + +package com.google.firebase.example; + +import android.app.Activity; +import android.content.SharedPreferences; + +/** Static utilties for saving and loading shared preference strings. */ +public final class SimplePersistentStorage { + private static final String PREF_NAME = "firebase_automated_test"; + /** + * Sets a given key's value in persistent storage to the given string. Specify null to delete the + * key. + */ + public static void setString(Activity activity, String key, String value) { + SharedPreferences pref = activity.getSharedPreferences(PREF_NAME, 0); + SharedPreferences.Editor editor = pref.edit(); + if (value != null) { + editor.putString(key, value); + } else { + editor.remove(key); + } + editor.commit(); + } + + /** Gets the value of the given key in persistent storage, or null if the key is not found. */ + public static String getString(Activity activity, String key) { + SharedPreferences pref = activity.getSharedPreferences(PREF_NAME, 0); + return pref.getString(key, null); + } + + private SimplePersistentStorage() {} +} diff --git a/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc b/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc new file mode 100644 index 0000000000..8ffbf9bb44 --- /dev/null +++ b/testing/test_framework/src/desktop/desktop_firebase_test_framework.cc @@ -0,0 +1,52 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 "firebase_test_framework.h" // NOLINT + +namespace firebase_test_framework { + +using app_framework::LogWarning; + +bool FirebaseTest::SendHttpGetRequest( + const char* url, const std::map& headers, + int* response_code, std::string* response_str) { + LogWarning("SendHttpGetRequest is not implemented on desktop."); + return false; +} + +bool FirebaseTest::SendHttpPostRequest( + const char* url, const std::map& headers, + const std::string& post_body, int* response_code, + std::string* response_str) { + LogWarning("SendHttpPostRequest is not implemented on desktop."); + return false; +} + +bool FirebaseTest::OpenUrlInBrowser(const char* url) { + LogWarning("OpenUrlInBrowser is not implemented on desktop."); + return false; +} + +bool FirebaseTest::SetPersistentString(const char* key, const char* value) { + LogWarning("SetPersistentString is not implemented on desktop."); + return false; +} + +bool FirebaseTest::GetPersistentString(const char* key, + std::string* value_out) { + LogWarning("GetPersistentString is not implemented on desktop."); + return false; +} + +} // namespace firebase_test_framework diff --git a/testing/test_framework/src/firebase_test_framework.cc b/testing/test_framework/src/firebase_test_framework.cc new file mode 100644 index 0000000000..d34f0bc2c6 --- /dev/null +++ b/testing/test_framework/src/firebase_test_framework.cc @@ -0,0 +1,221 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 "firebase_test_framework.h" // NOLINT + +#include + +#include "firebase/future.h" + +namespace firebase { +namespace internal { +// Borrow Firebase's internal Base64 encoder and decoder. +extern bool Base64Encode(const std::string& input, std::string* output); +extern bool Base64Decode(const std::string& input, std::string* output); +} // namespace internal +} // namespace firebase + +namespace firebase_test_framework { + +int FirebaseTest::argc_ = 0; +char** FirebaseTest::argv_ = nullptr; +bool FirebaseTest::found_config_ = false; + +FirebaseTest::FirebaseTest() : app_(nullptr) {} + +FirebaseTest::~FirebaseTest() { assert(app_ == nullptr); } + +void FirebaseTest::SetUp() {} + +void FirebaseTest::TearDown() { + if (HasFailure()) { + app_framework::SetPreserveFullLog(false); + app_framework::LogError( + "Test %s failed.\nFull test log:\n%s", + ::testing::UnitTest::GetInstance()->current_test_info()->name(), + "========================================================"); + app_framework::SetPreserveFullLog(true); + app_framework::AddToFullLog( + "========================================================\n"); + app_framework::OutputFullLog(); + } else { + app_framework::ClearFullLog(); + } +} + +void FirebaseTest::FindFirebaseConfig(const char* try_directory) { +#if !defined(__ANDROID__) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + static const char kDefaultGoogleServicesPath[] = "google-services.json"; + + if (!found_config_) { + if (try_directory[0] != '\0' && app_framework::FileExists(try_directory)) { + app_framework::ChangeToFileDirectory(try_directory); + } else if (app_framework::FileExists(kDefaultGoogleServicesPath)) { + // It's in the current directory, don't do anything. + } else { + // Try the directory the binary is in. + app_framework::ChangeToFileDirectory(argv_[0]); + } + // Only do this once. + found_config_ = true; + } +#endif // !defined(__ANDROID__) && !(defined(TARGET_OS_IPHONE) && + // TARGET_OS_IPHONE) + (void)try_directory; +} + +void FirebaseTest::InitializeApp() { + if (app_) return; // Already initialized. + + app_framework::LogDebug("Initialize Firebase App."); + +#if defined(__ANDROID__) + app_ = ::firebase::App::Create(app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + app_ = ::firebase::App::Create(); +#endif // defined(__ANDROID__) +} + +void FirebaseTest::TerminateApp() { + if (!app_) return; // Already terminated. + + app_framework::LogDebug("Shutdown Firebase App."); + delete app_; + app_ = nullptr; +} + +bool FirebaseTest::WaitForCompletion(const firebase::FutureBase& future, + const char* name, int expected_error) { + app_framework::LogDebug("WaitForCompletion %s", name); + while (future.status() == firebase::kFutureStatusPending) { + app_framework::ProcessEvents(100); + } + EXPECT_EQ(future.status(), firebase::kFutureStatusComplete) + << name << " returned an invalid status."; + EXPECT_EQ(future.error(), expected_error) + << name << " returned error " << future.error() << ": " + << future.error_message(); + return (future.status() == firebase::kFutureStatusComplete && + future.error() == expected_error); +} + +static void VariantToStringInternal(const firebase::Variant& variant, + std::ostream& out, + const std::string& indent) { + if (variant.is_null()) { + out << "null"; + } else if (variant.is_int64()) { + out << variant.int64_value(); + } else if (variant.is_double()) { + out << variant.double_value(); + } else if (variant.is_bool()) { + out << (variant.bool_value() ? "true" : "false"); + } else if (variant.is_string()) { + out << variant.string_value(); + } else if (variant.is_blob()) { + out << "blob[" << variant.blob_size() << "] = <"; + char hex[3]; + for (size_t i = 0; i < variant.blob_size(); ++i) { + snprintf(hex, sizeof(hex), "%02x", variant.blob_data()[i]); + if (i != 0) out << " "; + out << hex; + } + out << ">"; + } else if (variant.is_vector()) { + out << "[" << std::endl; + const auto& v = variant.vector(); + for (auto it = v.begin(); it != v.end(); ++it) { + out << indent + " "; + VariantToStringInternal(*it, out, indent + " "); + auto next_it = it; + next_it++; + if (next_it != v.end()) out << ","; + out << std::endl; + } + out << "]"; + } else if (variant.is_map()) { + out << "[" << std::endl; + const auto& m = variant.map(); + for (auto it = m.begin(); it != m.end(); ++it) { + out << indent + " "; + VariantToStringInternal(it->first, out, indent + " "); + out << ": "; + VariantToStringInternal(it->second, out, indent + " "); + auto next_it = it; + next_it++; + if (next_it != m.end()) out << ","; + out << std::endl; + } + out << "]"; + } else { + out << ""; + } +} + +std::string FirebaseTest::VariantToString(const firebase::Variant& variant) { + std::ostringstream out; + VariantToStringInternal(variant, out, ""); + return out.str(); +} + +bool FirebaseTest::IsUserInteractionAllowed() { + // In the trivial case, just check whether we are logging to file. If not, + // assume interaction is allowed. + return !app_framework::IsLoggingToFile(); +} + +bool FirebaseTest::Base64Encode(const std::string& input, std::string* output) { + return ::firebase::internal::Base64Encode(input, output); +} + +bool FirebaseTest::Base64Decode(const std::string& input, std::string* output) { + return ::firebase::internal::Base64Decode(input, output); +} + +class LogTestEventListener : public testing::EmptyTestEventListener { + public: + void OnTestPartResult( + const testing::TestPartResult& test_part_result) override { + if (test_part_result.failed() && test_part_result.message()) { + app_framework::AddToFullLog(test_part_result.message()); + app_framework::AddToFullLog("\n"); + } + }; +}; + +} // namespace firebase_test_framework + +namespace firebase { +// gtest requires that the operator<< be in the same namespace as the item you +// are outputting. +std::ostream& operator<<(std::ostream& os, const Variant& v) { + return os << firebase_test_framework::FirebaseTest::VariantToString(v); +} +} // namespace firebase + +extern "C" int common_main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + firebase_test_framework::FirebaseTest::SetArgs(argc, argv); + app_framework::SetLogLevel(app_framework::kInfo); + // Anything below the given log level will be preserved, and printed out in + // the event of test failure. + app_framework::SetPreserveFullLog(true); + ::testing::TestEventListeners& listeners = + ::testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new firebase_test_framework::LogTestEventListener()); + int result = RUN_ALL_TESTS(); + + return result; +} diff --git a/testing/test_framework/src/firebase_test_framework.h b/testing/test_framework/src/firebase_test_framework.h new file mode 100644 index 0000000000..4beb4fc492 --- /dev/null +++ b/testing/test_framework/src/firebase_test_framework.h @@ -0,0 +1,180 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 FIREBASE_TEST_FRAMEWORK_H_ // NOLINT +#define FIREBASE_TEST_FRAMEWORK_H_ // NOLINT + +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/future.h" +#include "firebase/internal/platform.h" +#include "firebase/util.h" +#include "firebase/variant.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace firebase_test_framework { + +// Use this macro to skip an entire test if it requires interactivity and we are +// not running in interactive mode (for example, on FTL). +#define TEST_REQUIRES_USER_INTERACTION \ + if (!IsUserInteractionAllowed()) { \ + app_framework::LogInfo("Skipping %s, as it requires user interaction.", \ + test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } + +#if TARGET_OS_IPHONE +#define TEST_REQUIRES_USER_INTERACTION_ON_IOS TEST_REQUIRES_USER_INTERACTION +#define TEST_REQUIRES_USER_INTERACTION_ON_ANDROID ((void)0) +#elif defined(ANDROID) +#define TEST_REQUIRES_USER_INTERACTION_ON_IOS ((void)0) +#define TEST_REQUIRES_USER_INTERACTION_ON_ANDROID TEST_REQUIRES_USER_INTERACTION +#else +#define TEST_REQUIRES_USER_INTERACTION_ON_IOS ((void)0) +#define TEST_REQUIRES_USER_INTERACTION_ON_ANDROID ((void)0) +#endif // TARGET_OS_IPHONE + +#if !defined(ANDROID) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +#define SKIP_TEST_ON_DESKTOP \ + { \ + app_framework::LogInfo("Skipping %s on desktop.", test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } +#else +#define SKIP_TEST_ON_DESKTOP ((void)0) +#endif // !defined(ANDROID) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + +#if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +#define SKIP_TEST_ON_MOBILE \ + { \ + app_framework::LogInfo("Skipping %s on mobile.", test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } +#else +#define SKIP_TEST_ON_MOBILE ((void)0) +#endif // defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + +#if defined(STLPORT) +#define SKIP_TEST_IF_USING_STLPORT \ + { \ + app_framework::LogInfo("Skipping %s due to incompatibility with STLPort.", \ + test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } +#else +#define SKIP_TEST_IF_USING_STLPORT ((void)0) +#endif // defined(STLPORT) + +#define KNOWN_FAILURE(explanation) \ + { FAIL() << test_info_->name() << " has a known failure: " << explanation; } + +#if FIREBASE_PLATFORM_LINUX || FIREBASE_PLATFORM_OSX +#define DEATHTEST_SIGABRT "SIGABRT" +#else +#define DEATHTEST_SIGABRT "" +#endif + +class FirebaseTest : public testing::Test { + public: + FirebaseTest(); + ~FirebaseTest() override; + + void SetUp() override; + void TearDown() override; + + // Check the given directory, the current directory, and the directory + // containing the binary for google-services.json, and change to whichever + // directory contains it. + static void FindFirebaseConfig(const char* try_directory); + + static void SetArgs(int argc, char* argv[]) { + argc_ = argc; + argv_ = argv; + } + + // Convert a Variant into a string (including all nested Variants) for + // debugging. + static std::string VariantToString(const firebase::Variant& variant); + + protected: + // Set up firebase::App with default settings. + void InitializeApp(); + // Shut down firebase::App. + void TerminateApp(); + + // Returns true if interactive tests are allowed, false if only + // fully-automated tests should be run. + bool AreInteractiveTestsAllowed(); + + // Get a persistent string value that was previously set via + // SetPersistentString. Returns true if the value was set, false if not or if + // something went wrong. + static bool GetPersistentString(const char* key, std::string* value_out); + // Set a persistent string value that can be accessed the next time the test + // loads. Specify nullptr for value to delete the key. Returns true if + // successful, false if something went wrong. + static bool SetPersistentString(const char* key, const char* value); + + // Returns true if the future completed as expected, fails the test and + // returns false otherwise. + static bool WaitForCompletion(const firebase::FutureBase& future, + const char* name, int expected_error = 0); + + // Blocking HTTP request helper function, for testing only. + static bool SendHttpGetRequest( + const char* url, const std::map& headers, + int* response_code, std::string* response); + + // Blocking HTTP request helper function, for testing only. + static bool SendHttpPostRequest( + const char* url, const std::map& headers, + const std::string& post_body, int* response_code, std::string* response); + + // Open a URL in a browser window, for testing only. + static bool OpenUrlInBrowser(const char* url); + + // Returns true if we can run tests that require interaction, false if not. + static bool IsUserInteractionAllowed(); + + // Encode a binary string to base64. Returns true if the encoding succeeded, + // false if it failed. + static bool Base64Encode(const std::string& input, std::string* output); + // Decode a base64 string to binary. Returns true if the decoding succeeded, + // false if it failed. + static bool Base64Decode(const std::string& input, std::string* output); + + firebase::App* app_; + static int argc_; + static char** argv_; + static bool found_config_; +}; + +} // namespace firebase_test_framework + +namespace firebase { +// Define an operator<< for Variant so that googletest can output its values +// nicely. +std::ostream& operator<<(std::ostream& os, const Variant& v); +} // namespace firebase + +extern "C" int common_main(int argc, char* argv[]); + +#endif // FIREBASE_TEST_FRAMEWORK_H_ // NOLINT diff --git a/testing/test_framework/src/ios/ios_firebase_test_framework.mm b/testing/test_framework/src/ios/ios_firebase_test_framework.mm new file mode 100644 index 0000000000..d36f141d99 --- /dev/null +++ b/testing/test_framework/src/ios/ios_firebase_test_framework.mm @@ -0,0 +1,190 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 "firebase_test_framework.h" // NOLINT + +#import +#import + +namespace firebase_test_framework { + +using app_framework::LogDebug; +using app_framework::LogError; +using app_framework::ProcessEvents; + +// Default HTTP timeout of 1 minute. +const int kHttpTimeoutSeconds = 60; + +// A simple helper function for performing synchronous HTTP/HTTPS requests, used for testing +// purposes only. +static bool SendHttpRequest(const char* method, const char* url, + const std::map& headers, + const std::string& post_body, int* response_code, + std::string* response_str) { + NSMutableURLRequest* url_request = [[NSMutableURLRequest alloc] init]; + url_request.URL = [NSURL URLWithString:@(url)]; + url_request.HTTPMethod = @(method); + url_request.timeoutInterval = kHttpTimeoutSeconds; + if (strcmp(method, "POST") == 0) { + url_request.HTTPBody = [NSData dataWithBytes:post_body.c_str() length:post_body.length()]; + } + // Set all the headers. + for (auto i = headers.begin(); i != headers.end(); ++i) { + [url_request addValue:@(i->second.c_str()) forHTTPHeaderField:@(i->first.c_str())]; + } + __block dispatch_semaphore_t sem = dispatch_semaphore_create(0); + __block NSError* response_error; + __block NSHTTPURLResponse* http_response; + __block NSData* response_data; + LogDebug("Sending HTTP %s request to %s", method, url); + @try { + [[NSURLSession.sharedSession + dataTaskWithRequest:url_request + completionHandler:^(NSData* __nullable data, NSURLResponse* __nullable response, + NSError* __nullable error) { + response_data = data; + http_response = (NSHTTPURLResponse*)(response); + response_error = error; + dispatch_semaphore_signal(sem); + }] resume]; + } @catch (NSException* e) { + LogError("NSURLSession exception: %s", e.reason.UTF8String); + return false; + } + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + + LogDebug("HTTP status code %ld", http_response.statusCode); + if (http_response && response_code) { + *response_code = static_cast(http_response.statusCode); + } + std::string response_text = + response_data.bytes + ? std::string(reinterpret_cast(response_data.bytes), response_data.length) + : std::string(); + LogDebug("Got response: %s", response_text.c_str()); + if (response_str) { + *response_str = response_text; + } + if (response_error) { + LogError("HTTP error: %s", response_error.localizedDescription.UTF8String); + return false; + } + return true; +} + +// Blocking HTTP request helper function, for testing only. +bool FirebaseTest::SendHttpGetRequest(const char* url, + const std::map& headers, + int* response_code, std::string* response_str) { + return SendHttpRequest("GET", url, headers, "", response_code, response_str); +} + +bool FirebaseTest::SendHttpPostRequest(const char* url, + const std::map& headers, + const std::string& post_body, int* response_code, + std::string* response_str) { + return SendHttpRequest("POST", url, headers, post_body, response_code, response_str); +} + +bool FirebaseTest::OpenUrlInBrowser(const char* url) { + if (strncmp(url, "data:", strlen("data:")) == 0) { + // Workaround because Safari can't load data: URLs by default. + // Instead, copy the URL to the clipboard and ask the user to paste it into Safari. + + // data: URLs are in the format data:text/html, + // Preserve everything until the first comma, then URL-encode the rest. + const char* payload = strchr(url, ','); + if (payload == nullptr) { + return false; + } + payload++; // Move past the comma. + std::string scheme_and_encoding(url); + scheme_and_encoding.resize(payload - url); + UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; + pasteboard.string = [@(scheme_and_encoding.c_str()) + stringByAppendingString:[@(payload) stringByAddingPercentEncodingWithAllowedCharacters: + [NSCharacterSet URLHostAllowedCharacterSet]]]; + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + UIAlertController* alert = + [UIAlertController alertControllerWithTitle:@"Paste URL" + message:@"Opening Safari. Please tap twice on the " + @"address bar and select \"Paste and Go\"." + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* ok = [UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) { + dispatch_semaphore_signal(sem); + }]; + [alert addAction:ok]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert + animated:YES + completion:nil]; + }); + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + return OpenUrlInBrowser("http://"); + } else { + // Not a data: URL, load it normally. + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + __block BOOL succeeded = NO; + NSURL* nsurl = [NSURL URLWithString:@(url)]; + [UIApplication.sharedApplication openURL:nsurl + options:[NSDictionary dictionary] + completionHandler:^(BOOL success) { + succeeded = success; + dispatch_semaphore_signal(sem); + }]; + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + return succeeded ? true : false; + } +} + +bool FirebaseTest::SetPersistentString(const char* key, const char* value) { + if (!key) { + LogError("SetPersistentString: null key is not allowed."); + return false; + } + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + if (!defaults) { + return false; + } + if (value) { + [defaults setObject:@(value) forKey:@(key)]; + } else { + // If value is null, remove this key. + [defaults removeObjectForKey:@(key)]; + } + [defaults synchronize]; + return true; +} + +bool FirebaseTest::GetPersistentString(const char* key, std::string* value_out) { + if (!key) { + return false; + } + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + if (!defaults) { + return false; + } + if (![defaults objectForKey:@(key)]) { + return false; // for missing key + } + NSString* str = [defaults stringForKey:@(key)]; + if (value_out) { + *value_out = std::string(str.UTF8String); + } + return true; +} + +} // namespace firebase_test_framework