1#!/usr/bin/env bash
2# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15# ==============================================================================
16#
17# Common Bash functions used by build scripts
18
19COLOR_NC='\033[0m'
20COLOR_BOLD='\033[1m'
21COLOR_LIGHT_GRAY='\033[0;37m'
22COLOR_GREEN='\033[0;32m'
23COLOR_RED='\033[0;31m'
24
25die() {
26  # Print a message and exit with code 1.
27  #
28  # Usage: die <error_message>
29  #   e.g., die "Something bad happened."
30
31  echo $@
32  exit 1
33}
34
35realpath() {
36  # Get the real path of a file
37  # Usage: realpath <file_path>
38
39  if [[ $# != "1" ]]; then
40    die "realpath: incorrect usage"
41  fi
42
43  [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
44}
45
46to_lower () {
47  # Convert string to lower case.
48  # Usage: to_lower <string>
49
50  echo "$1" | tr '[:upper:]' '[:lower:]'
51}
52
53calc_elapsed_time() {
54  # Calculate elapsed time. Takes nanosecond format input of the kind output
55  # by date +'%s%N'
56  #
57  # Usage: calc_elapsed_time <START_TIME> <END_TIME>
58
59  if [[ $# != "2" ]]; then
60    die "calc_elapsed_time: incorrect usage"
61  fi
62
63  START_TIME=$1
64  END_TIME=$2
65
66  if [[ ${START_TIME} == *"N" ]]; then
67    # Nanosecond precision not available
68    START_TIME=$(echo ${START_TIME} | sed -e 's/N//g')
69    END_TIME=$(echo ${END_TIME} | sed -e 's/N//g')
70    ELAPSED="$(expr ${END_TIME} - ${START_TIME}) s"
71  else
72    ELAPSED="$(expr $(expr ${END_TIME} - ${START_TIME}) / 1000000) ms"
73  fi
74
75  echo ${ELAPSED}
76}
77
78run_in_directory() {
79  # Copy the test script to a destination directory and run the test there.
80  # Write test log to a log file.
81  #
82  # Usage: run_in_directory <DEST_DIR> <LOG_FILE> <TEST_SCRIPT>
83  #                         [ARGS_FOR_TEST_SCRIPT]
84
85  if [[ $# -lt "3" ]]; then
86    die "run_in_directory: incorrect usage"
87  fi
88
89  DEST_DIR="$1"
90  LOG_FILE="$2"
91  TEST_SCRIPT="$3"
92  shift 3
93  SCRIPT_ARGS=("$@")
94
95  # Get the absolute path of the log file
96  LOG_FILE_ABS=$(realpath "${LOG_FILE}")
97
98  cp "${TEST_SCRIPT}" "${DEST_DIR}"/
99  SCRIPT_BASENAME=$(basename "${TEST_SCRIPT}")
100
101  if [[ ! -f "${DEST_DIR}/${SCRIPT_BASENAME}" ]]; then
102    echo "FAILED to copy script ${TEST_SCRIPT} to temporary directory "\
103"${DEST_DIR}"
104    return 1
105  fi
106
107  pushd "${DEST_DIR}" > /dev/null
108
109  "${TIMEOUT_BIN}" --preserve-status ${TIMEOUT} \
110    "${PYTHON_BIN_PATH}" "${SCRIPT_BASENAME}" ${SCRIPT_ARGS[@]} 2>&1 \
111    > "${LOG_FILE_ABS}"
112
113  rm -f "${SCRIPT_BASENAME}"
114  popd > /dev/null
115
116  if [[ $? != 0 ]]; then
117    echo "Test \"${SCRIPT_BASENAME}\" FAILED"
118    return 1
119  fi
120
121  return 0
122}
123
124
125test_runner() {
126  # Run a suite of tests, print failure logs (if any), wall-time each test,
127  # and show the summary at the end.
128  #
129  # Usage: test_runner <TEST_DESC> <ALL_TESTS> <TEST_BLACKLIST> <LOGS_DIR>
130  # e.g.,  test_runner "Tutorial test-on-install" \
131  #                    "test1 test2 test3" "test2 test3" "/tmp/log_dir"
132
133  if [[ $# != "4" ]]; then
134    die "test_runner: incorrect usage"
135  fi
136
137  TEST_DESC=$1
138  ALL_TESTS_STR=$2
139  TEST_BLACKLIST_SR=$3
140  LOGS_DIR=$4
141
142  NUM_TESTS=$(echo "${ALL_TESTS_STR}" | wc -w)
143  ALL_TESTS=(${ALL_TESTS_STR})
144
145  COUNTER=0
146  PASSED_COUNTER=0
147  FAILED_COUNTER=0
148  FAILED_TESTS=""
149  FAILED_TEST_LOGS=""
150  SKIPPED_COUNTER=0
151  for CURR_TEST in ${ALL_TESTS[@]}; do
152    ((COUNTER++))
153    STAT_STR="(${COUNTER} / ${NUM_TESTS})"
154
155    if [[ "${TEST_BLACKLIST_STR}" == *"${CURR_TEST}"* ]]; then
156      ((SKIPPED_COUNTER++))
157      echo "${STAT_STR} Blacklisted ${TEST_DESC} SKIPPED: ${CURR_TEST}"
158      continue
159    fi
160
161    START_TIME=$(date +'%s%N')
162
163    LOG_FILE="${LOGS_DIR}/${CURR_TEST}.log"
164    rm -rf ${LOG_FILE} ||
165    die "Unable to remove existing log file: ${LOG_FILE}"
166
167    "test_${CURR_TEST}" "${LOG_FILE}"
168    TEST_RESULT=$?
169
170    END_TIME=$(date +'%s%N')
171    ELAPSED_TIME=$(calc_elapsed_time "${START_TIME}" "${END_TIME}")
172
173    if [[ ${TEST_RESULT} == 0 ]]; then
174      ((PASSED_COUNTER++))
175      echo "${STAT_STR} ${TEST_DESC} PASSED: ${CURR_TEST} "\
176  "(Elapsed time: ${ELAPSED_TIME})"
177    else
178      ((FAILED_COUNTER++))
179      FAILED_TESTS="${FAILED_TESTS} ${CURR_TEST}"
180      FAILED_TEST_LOGS="${FAILED_TEST_LOGS} ${LOG_FILE}"
181
182      echo "${STAT_STR} ${TEST_DESC} FAILED: ${CURR_TEST} "\
183  "(Elapsed time: ${ELAPSED_TIME})"
184
185      echo "============== BEGINS failure log content =============="
186      cat ${LOG_FILE}
187      echo "============== ENDS failure log content =============="
188      echo ""
189    fi
190  done
191
192  echo "${NUM_TUT_TESTS} ${TEST_DESC} test(s): "\
193  "${PASSED_COUNTER} passed; ${FAILED_COUNTER} failed; ${SKIPPED_COUNTER} skipped"
194
195  if [[ ${FAILED_COUNTER} -eq 0  ]]; then
196    echo ""
197    echo "${TEST_DESC} SUCCEEDED"
198
199    exit 0
200  else
201    echo "FAILED test(s):"
202    FAILED_TEST_LOGS=($FAILED_TEST_LOGS)
203    FAIL_COUNTER=0
204    for TEST_NAME in ${FAILED_TESTS}; do
205      echo "  ${TEST_DESC} (Log @: ${FAILED_TEST_LOGS[${FAIL_COUNTER}]})"
206      ((FAIL_COUNTER++))
207    done
208
209    echo ""
210    die "${TEST_DESC} FAILED"
211  fi
212}
213
214configure_android_workspace() {
215  # Modify the WORKSPACE file.
216  # Note: This is workaround. This should be done by bazel.
217  if grep -q '^android_sdk_repository' WORKSPACE && grep -q '^android_ndk_repository' WORKSPACE; then
218    echo "You probably have your WORKSPACE file setup for Android."
219  else
220    if [ -z "${ANDROID_API_LEVEL}" -o -z "${ANDROID_BUILD_TOOLS_VERSION}" ] || \
221        [ -z "${ANDROID_SDK_HOME}" -o -z "${ANDROID_NDK_HOME}" ]; then
222      echo "ERROR: Your WORKSPACE file does not seems to have proper android"
223      echo "       configuration and not all the environment variables expected"
224      echo "       inside ci_build android docker container are set."
225      echo "       Please configure it manually. See: https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android/README.md"
226    else
227      cat << EOF >> WORKSPACE
228android_sdk_repository(
229    name = "androidsdk",
230    api_level = ${ANDROID_API_LEVEL},
231    build_tools_version = "${ANDROID_BUILD_TOOLS_VERSION}",
232    path = "${ANDROID_SDK_HOME}",
233)
234
235android_ndk_repository(
236    name="androidndk",
237    path="${ANDROID_NDK_HOME}",
238    api_level=14)
239EOF
240    fi
241  fi
242}
243