#!/bin/bash # RuniOSUnitTestsUnderSimulator.sh.sh # Copyright 2008-2012 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy # of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. # # Runs all unittests through the iOS simulator. We don't handle running them # on the device. To run on the device just choose "run". set -o errexit set -o nounset # Uncomment the next line to trace execution. #set -o verbose # GTM_DEVICE_TYPE - # Set to 'iPhone' or 'iPad' to control which simulator is used. GTM_DEVICE_TYPE=${GTM_DEVICE_TYPE:=iPhone} # GTM_SIMULATOR_SDK_VERSION # Set to something like 5.1 to use a specific SDK version (the needed # simulator support must be installed). Use 'default' to get the dev tools # default value. GTM_SIMULATOR_SDK_VERSION=${GTM_SIMULATOR_SDK_VERSION:=default} # GTM_SIMULATOR_START_TIMEOUT # Controls the simulator startup timeout. Things like machine load, running # on a VM, etc.; can cause the startup to take longer. GTM_SIMULATOR_START_TIMEOUT=${GTM_SIMULATOR_START_TIMEOUT:=120} # GTM_KILL_SLEEP_TIME # Controls the time the script will sleep when it kills a process. Things # like machine load, running on a VM, etc.; can cause the time for things to # die to take longer. GTM_KILL_SLEEP_TIME=${GTM_KILL_SLEEP_TIME:=5} # GTM_SIMULATOR_USER_HOME - # Root directory for simulator file system. Allows persistence across runs. GTM_SIMULATOR_USER_HOME=${GTM_SIMULATOR_USER_HOME:=default} # GTM_SIMULATOR_EXTRA_ENV - # Space separated set env variables in format of "KEY1=value1 KEY2=value2" GTM_SIMULATOR_EXTRA_ENV=${GTM_SIMULATOR_EXTRA_ENV:=default} # GTM_DISABLE_TERMINATION # Set to a non-zero value so that the app doesn't terminate when it's finished # running tests. This is useful when using it with external tools such # as Instruments. # GTM_REMOVE_GCOV_DATA # Before starting the test, remove any *.gcda files for the current run so # you won't get errors when the source file has changed and the data can't # be merged. # GTM_REMOVE_GCOV_DATA=${GTM_REMOVE_GCOV_DATA:=0} # GTM_USE_TEST_AFTER_BUILD # When set to 1, tests are run only when TEST_AFTER_BUILD is set to "YES". # This can be used to have tests run as an after build step when running # from the command line, but not when running from within XCode. GTM_USE_TEST_AFTER_BUILD=${GTM_USE_TEST_AFTER_BUILD:=0} function realpath() { OLDPWD=${PWD} cd "$(dirname "$1")" TARGET="$(readlink "$(basename "$1")")" while [[ -n "${TARGET}" ]]; do cd "$(dirname "${TARGET}")" TARGET=$(readlink "$(basename "$1")") done REALPATH="${PWD}/$(basename "$1")" cd "${OLDPWD}" echo "${REALPATH}" } readonly ScriptDir=$(dirname "$(echo $0 | sed -e "s,^\([^/]\),$(pwd)/\1,")") readonly ScriptName=$(basename "$0") readonly ThisScript="${ScriptDir}/${ScriptName}" # iossim fails if it's behind the right combination of symlinks readonly SimExecutable="$(realpath "${ScriptDir}/iossim")" # Simulator process name changes from Xcode 6. if [[ ${XCODE_VERSION_MINOR} -ge "0600" ]]; then readonly SimulatorProcessName='iOS Simulator' else readonly SimulatorProcessName='iPhone Simulator' fi # Variables that follow Xcode unittesting conventions readonly TEST_BUNDLE_PATH="${TEST_BUNDLE_PATH:=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.${WRAPPER_EXTENSION}}" TEST_HOST="${TEST_HOST:=}" XCODE_VERSION_MINOR=${XCODE_VERSION_MINOR:=0000} # Old Xcode versions don't set TEST_AFTER_BUILD, default it to avoid errors # for unset variables. TEST_AFTER_BUILD=${TEST_AFTER_BUILD:=NO} # Some helpers. GTMXcodeNote() { echo "${ThisScript}:${1}: note: GTM ${2}" } GTMXcodeWarning() { echo "${ThisScript}:${1}: warning: GTM ${2}" } GTMXcodeError() { echo "${ThisScript}:${1}: error: GTM ${2}" } GTMFakeUnitTestingMsg() { echo "${DEVELOPER_DIR}/Tools/RunPlatformUnitTests.include:${1}: ${2}: ${3}" } GTMKillNamedAndWait() { # If there is something killed, sleep for few seconds to let the process # spin down so it isn't still seen as running when the next thing tries to # launch it. /usr/bin/killall "${1}" 2> /dev/null && sleep "${GTM_KILL_SLEEP_TIME}" || true } GTMKillSimulator() { GTMKillNamedAndWait "${SimulatorProcessName}" } GTMResetSimulator() { GTMKillSimulator device_id=`xcrun simctl list | grep "${GTM_DEVICE_TYPE} (" | sed -n 2p | \ cut -d "(" -f2 | cut -d ")" -f1` xcrun simctl erase $device_id || true } # Honor TEST_AFTER_BUILD if requested. if [[ "$GTM_USE_TEST_AFTER_BUILD" == 1 && "$TEST_AFTER_BUILD" == "NO" ]]; then GTMXcodeNote ${LINENO} "Skipping running of unittests since TEST_AFTER_BUILD=NO." exit 0 fi # Only support simulator builds. if [[ "${PLATFORM_NAME}" != "iphonesimulator" ]]; then GTMXcodeNote ${LINENO} "Skipping running of unittests for device build." exit 0 fi # Make sure the iossim executable exists and is executable. if [[ ! -x "${SimExecutable}" ]]; then GTMXcodeError ${LINENO} "Unable to run tests: ${SimExecutable} was not found/executable." exit 1 fi # Make sure the test bundle (and test host) exists. if [[ ! -e "${TEST_BUNDLE_PATH}" ]]; then GTMXcodeError ${LINENO} "Unable to find test bundle: ${TEST_BUNDLE_PATH}" exit 1 fi if [[ -n "${TEST_HOST}" ]]; then if [[ ! -e "${TEST_HOST}" ]]; then GTMXcodeError ${LINENO} "Unable to find test host: ${TEST_HOST}" exit 1 fi fi # Sanity check Xcode version. if [[ "${XCODE_VERSION_MINOR}" -lt "0430" ]]; then GTMXcodeWarning ${LINENO} "Unit testing process not supported on Xcode < 4.3 (${XCODE_VERSION_MINOR})" fi # We kill the iPhone Simulator because otherwise we run into issues where # the unittests fail becuase the simulator is currently running, and # at this time the iOS SDK won't allow two simulators running at the same # time. GTMKillSimulator if [[ $GTM_REMOVE_GCOV_DATA -ne 0 ]]; then if [[ "${OBJECT_FILE_DIR}-${CURRENT_VARIANT}" != "-" ]]; then if [[ -d "${OBJECT_FILE_DIR}-${CURRENT_VARIANT}" ]]; then GTMXcodeNote ${LINENO} "Removing any .gcda files" (cd "${OBJECT_FILE_DIR}-${CURRENT_VARIANT}" && \ find . -type f -name "*.gcda" -print0 | xargs -0 rm -f ) fi fi fi # # Build up the command line to run. # GTM_TEST_APP_NAME= GTM_TEST_COMMAND=( "${SimExecutable}" "-d" "${GTM_DEVICE_TYPE}" "-t" "${GTM_SIMULATOR_START_TIMEOUT}" ) if [[ "${GTM_SIMULATOR_SDK_VERSION}" != "default" ]] ; then GTM_TEST_COMMAND+=( "-s" "${GTM_SIMULATOR_SDK_VERSION}" ) fi if [[ "${GTM_SIMULATOR_USER_HOME}" != "default" ]]; then GTM_TEST_COMMAND+=( "-u" "${GTM_SIMULATOR_USER_HOME}" ) fi if [[ "${GTM_SIMULATOR_EXTRA_ENV}" != "default" ]]; then EXTRA_ENV_ARRAY=(${GTM_SIMULATOR_EXTRA_ENV}) for i in "${!EXTRA_ENV_ARRAY[@]}" do GTM_TEST_COMMAND+=( "-e" ${EXTRA_ENV_ARRAY[i]} ) done fi if [[ -n "${TEST_HOST}" ]]; then # When using a test host, it is usually set to the executable within the app # bundle, back up one to point at the bundle. GTM_TEST_APP_NAME=$(basename "${TEST_HOST}") TEST_HOST_EXTENSION="${GTM_TEST_APP_NAME##*.}" if [[ "${TEST_HOST_EXTENSION}" != "app" ]] ; then TEST_HOST=$(dirname "${TEST_HOST}") else # Drop the extension. GTM_TEST_APP_NAME="${GTM_TEST_APP_NAME%.*}" fi # Yes the DYLD_INSERT_LIBRARIES value below looks odd, that is found from # looking at what Xcode sets when it invokes unittests directly. GTM_TEST_COMMAND+=( "-e" "DYLD_INSERT_LIBRARIES=/../../Library/PrivateFrameworks/IDEBundleInjection.framework/IDEBundleInjection" "-e" "XCInjectBundle=${TEST_BUNDLE_PATH}" "-e" "XCInjectBundleInto=${TEST_HOST}" "${TEST_HOST}" ) else GTM_TEST_APP_NAME=$(basename "${TEST_BUNDLE_PATH}") TEST_BUNDLE_EXTENSION="${GTM_TEST_APP_NAME##*.}" if [[ "${TEST_BUNDLE_EXTENSION}" == "app" ]] ; then # Drop the extension. GTM_TEST_APP_NAME="${GTM_TEST_APP_NAME%.*}" fi GTM_TEST_COMMAND+=( "${TEST_BUNDLE_PATH}" ) fi GTM_TEST_COMMAND+=( "-NSTreatUnknownArgumentsAsOpen" "NO" "-ApplePersistenceIgnoreState" "YES" "-SenTest" "All" ) # There was a test host, add the test bundle at the end as an arg to the app. if [[ -n "${TEST_HOST}" ]]; then GTM_TEST_COMMAND+=( "${TEST_BUNDLE_PATH}" ) fi # Kill the test host just to make sure it wasn't left running and will cause # problems. GTMKillNamedAndWait "${GTM_TEST_APP_NAME}" # These two lines seem to fake out Xcode just enough that its log parser acts # as though Xcode were running the unit test via the UI. This prevents false # failures based on lines including "error" and such (which tends to happen in # raw NSLogs in code). GTMFakeUnitTestingMsg ${LINENO} "note" "Started tests for architectures 'i386'" GTMFakeUnitTestingMsg ${LINENO} "note" "Running tests for architecture 'i386' (GC OFF)" set +e "${GTM_TEST_COMMAND[@]}" TEST_HOST_RESULT=$? set -e GTMKillSimulator GTMKillNamedAndWait "${GTM_TEST_APP_NAME}" # If the simulator fails to open with error FBSOpenApplicationErrorDomain:4, # reset the sim and try again (Known simulator issue for Xcode 6). if [ ${TEST_HOST_RESULT} -eq 4 ] && [ ${XCODE_VERSION_MINOR} -ge "0600" ]; then GTMFakeUnitTestingMsg ${LINENO} "note" "Simulator failed to open with result $TEST_HOST_RESULT, trying again." GTMResetSimulator set +e "${GTM_TEST_COMMAND[@]}" TEST_HOST_RESULT=$? set -e fi if [[ ${TEST_HOST_RESULT} -ne 0 ]]; then GTMXcodeError ${LINENO} "Tests failed." exit 1 fi exit 0