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# Build the Python PIP installation package for TensorFlow and install
18# the package.
19# The PIP installation is done using the --user flag.
20#
21# Usage:
22#   pip.sh CONTAINER_TYPE [--test_tutorials] [--integration_tests] [bazel flags]
23#
24# When executing the Python unit tests, the script obeys the shell
25# variables: TF_BUILD_BAZEL_CLEAN, TF_BUILD_INSTALL_EXTRA_PIP_PACKAGES,
26# NO_TEST_ON_INSTALL, PIP_TEST_ROOT, TF_NIGHTLY
27#
28# TF_BUILD_BAZEL_CLEAN, if set to any non-empty and non-0 value, directs the
29# script to perform bazel clean prior to main build and test steps.
30#
31# TF_BUILD_INSTALL_EXTRA_PIP_PACKAGES overrides the default extra pip packages
32# to be installed in virtualenv before run_pip_tests.sh is called. Multiple
33# pakcage names are separated with spaces.
34#
35# If NO_TEST_ON_INSTALL has any non-empty and non-0 value, the test-on-install
36# part will be skipped.
37#
38# If NO_TEST_USER_OPS has any non-empty and non-0 value, the testing of user-
39# defined ops against the installation will be skipped.
40#
41# If NO_TEST_TFDBG_BINARIES has any non-empty and non-0 value, the testing of
42# TensorFlow Debugger (tfdbg) binaries and examples will be skipped.
43#
44# If PIP_TEST_ROOT has a non-empty and a non-0 value, the whl files will be
45# placed in that directory.
46#
47# If TF_NIGHTLY has a non-empty and a non-0 value, the name of the project will
48# be changed to tf_nightly or tf_nightly_gpu.
49#
50# Any flags not listed in the usage above will be passed directly to Bazel.
51#
52# If the --test_tutorials flag is set, it will cause the script to run the
53# tutorial tests (see test_tutorials.sh) after the PIP
54# installation and the Python unit tests-on-install step. Likewise,
55# --integration_tests will cause the integration tests (integration_tests.sh)
56# to run.
57#
58
59# Helper function: Strip leading and trailing whitespaces
60str_strip () {
61  echo -e "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
62}
63
64# Fixed naming patterns for wheel (.whl) files given different python versions
65if [[ $(uname) == "Linux" ]]; then
66  declare -A WHL_TAGS
67  WHL_TAGS=(["2.7"]="cp27-none" ["3.4"]="cp34-cp34m" ["3.5"]="cp35-cp35m")
68fi
69
70
71INSTALL_EXTRA_PIP_PACKAGES=${TF_BUILD_INSTALL_EXTRA_PIP_PACKAGES}
72
73
74# Script directory
75SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
76source "${SCRIPT_DIR}/builds_common.sh"
77
78
79SKIP_RETURN_CODE=112
80
81
82# Get the command line arguments
83CONTAINER_TYPE=$( echo "$1" | tr '[:upper:]' '[:lower:]' )
84shift
85
86if [[ -n "${TF_BUILD_BAZEL_CLEAN}" ]] && \
87   [[ "${TF_BUILD_BAZEL_CLEAN}" != "0" ]]; then
88  echo "TF_BUILD_BAZEL_CLEAN=${TF_BUILD_BAZEL_CLEAN}: Performing 'bazel clean'"
89  bazel clean
90fi
91
92DO_TEST_USER_OPS=1
93if [[ -n "${NO_TEST_USER_OPS}" ]] && \
94   [[ "${NO_TEST_USER_OPS}" != "0" ]]; then
95  echo "NO_TEST_USER_OPS=${NO_TEST_USER_OPS}: Will skip testing of user ops"
96  DO_TEST_USER_OPS=0
97fi
98
99DO_TEST_TFDBG_BINARIES=1
100if [[ -n "${NO_TEST_TFDBG_BINARIES}" ]] && \
101   [[ "${NO_TEST_TFDBG_BINARIES}" != "0" ]]; then
102  echo "NO_TEST_TFDBG_BINARIES=${NO_TEST_TFDBG_BINARIES}: Will skip testing of tfdbg binaries"
103  DO_TEST_TFDBG_BINARIES=0
104fi
105
106DO_TEST_TUTORIALS=0
107DO_INTEGRATION_TESTS=0
108BAZEL_FLAGS=""
109while true; do
110  if [[ "${1}" == "--test_tutorials" ]]; then
111    DO_TEST_TUTORIALS=1
112  elif [[ "${1}" == "--integration_tests" ]]; then
113    DO_INTEGRATION_TESTS=1
114  else
115    BAZEL_FLAGS="${BAZEL_FLAGS} ${1}"
116  fi
117
118  shift
119  if [[ -z "${1}" ]]; then
120    break
121  fi
122done
123
124BAZEL_FLAGS=$(str_strip "${BAZEL_FLAGS}")
125
126if [[ -z "$GIT_TAG_OVERRIDE" ]]; then
127  BAZEL_FLAGS+=" --action_env=GIT_TAG_OVERRIDE"
128fi
129
130echo "Using Bazel flags: ${BAZEL_FLAGS}"
131
132PIP_BUILD_TARGET="//tensorflow/tools/pip_package:build_pip_package"
133GPU_FLAG=""
134if [[ ${CONTAINER_TYPE} == "cpu" ]] || \
135   [[ ${CONTAINER_TYPE} == "rocm" ]] || \
136   [[ ${CONTAINER_TYPE} == "debian.jessie.cpu" ]]; then
137  bazel build ${BAZEL_FLAGS} ${PIP_BUILD_TARGET} || \
138      die "Build failed."
139elif [[ ${CONTAINER_TYPE} == "gpu" ]]; then
140  bazel build ${BAZEL_FLAGS} ${PIP_BUILD_TARGET} || \
141      die "Build failed."
142  GPU_FLAG="--gpu"
143else
144  die "Unrecognized container type: \"${CONTAINER_TYPE}\""
145fi
146
147MAC_FLAG=""
148if [[ $(uname) == "Darwin" ]]; then
149  MAC_FLAG="--mac"
150fi
151
152
153# Check if in a virtualenv
154IN_VENV=$(python -c 'import sys; print("1" if hasattr(sys, "real_prefix") else "0")')
155# If still in a virtualenv, deactivate it first
156if [[ "$IN_VENV" == "1" ]]; then
157  echo "It appears that we are already in a virtualenv. Deactivating..."
158  deactivate || die "FAILED: Unable to deactivate from existing virtualenv"
159fi
160
161# Obtain the path to Python binary
162source tools/python_bin_path.sh
163
164# Assume: PYTHON_BIN_PATH is exported by the script above
165if [[ -z "$PYTHON_BIN_PATH" ]]; then
166  die "PYTHON_BIN_PATH was not provided. Did you run configure?"
167fi
168
169# Determine the major and minor versions of Python being used (e.g., 2.7)
170# This info will be useful for determining the directory of the local pip
171# installation of Python
172PY_MAJOR_MINOR_VER=$(${PYTHON_BIN_PATH} -V 2>&1 | awk '{print $NF}' | cut -d. -f-2)
173if [[ -z "${PY_MAJOR_MINOR_VER}" ]]; then
174  die "ERROR: Unable to determine the major.minor version of Python"
175fi
176
177echo "Python binary path to be used in PIP install: ${PYTHON_BIN_PATH} "\
178"(Major.Minor version: ${PY_MAJOR_MINOR_VER})"
179
180# Create a TF_NIGHTLY argument if this is a nightly build
181PROJECT_NAME="tensorflow"
182NIGHTLY_FLAG=""
183if [ -n "$TF_NIGHTLY" ]; then
184  PROJECT_NAME="tf_nightly"
185  NIGHTLY_FLAG="--nightly_flag"
186fi
187
188# Build PIP Wheel file
189# Set default pip file folder unless specified by env variable
190if [ -z "$PIP_TEST_ROOT" ]; then
191  PIP_TEST_ROOT="pip_test"
192fi
193PIP_WHL_DIR="${PIP_TEST_ROOT}/whl"
194PIP_WHL_DIR=$(realpath ${PIP_WHL_DIR})  # Get absolute path
195rm -rf ${PIP_WHL_DIR} && mkdir -p ${PIP_WHL_DIR}
196bazel-bin/tensorflow/tools/pip_package/build_pip_package ${PIP_WHL_DIR} ${GPU_FLAG} ${NIGHTLY_FLAG} || \
197    die "build_pip_package FAILED"
198
199WHL_PATH=$(ls ${PIP_WHL_DIR}/${PROJECT_NAME}*.whl)
200if [[ $(echo ${WHL_PATH} | wc -w) -ne 1 ]]; then
201  die "ERROR: Failed to find exactly one built TensorFlow .whl file in "\
202"directory: ${PIP_WHL_DIR}"
203fi
204
205# Print the size of the PIP wheel file.
206echo
207echo "Size of the PIP wheel file built: $(ls -l ${WHL_PATH} | awk '{print $5}')"
208echo
209
210# Rename the whl file properly so it will have the python
211# version tags and platform tags that won't cause pip install issues.
212if [[ $(uname) == "Linux" ]]; then
213  PY_TAGS=${WHL_TAGS[${PY_MAJOR_MINOR_VER}]}
214  PLATFORM_TAG=$(to_lower "$(uname)_$(uname -m)")
215# MAC has bash v3, which does not have associative array
216elif [[ $(uname) == "Darwin" ]]; then
217  if [[ ${PY_MAJOR_MINOR_VER} == "2.7" ]]; then
218    PY_TAGS="py2-none"
219  elif [[ ${PY_MAJOR_MINOR_VER} == "3.5" ]]; then
220    PY_TAGS="py3-none"
221  elif [[ ${PY_MAJOR_MINOR_VER} == "3.6" ]]; then
222    PY_TAGS="py3-none"
223  fi
224  PLATFORM_TAG="any"
225fi
226
227WHL_DIR=$(dirname "${WHL_PATH}")
228WHL_BASE_NAME=$(basename "${WHL_PATH}")
229
230if [[ -n "${PY_TAGS}" ]]; then
231  NEW_WHL_BASE_NAME=$(echo ${WHL_BASE_NAME} | cut -d \- -f 1)-\
232$(echo ${WHL_BASE_NAME} | cut -d \- -f 2)-${PY_TAGS}-${PLATFORM_TAG}.whl
233
234  if [[ ! -f "${WHL_DIR}/${NEW_WHL_BASE_NAME}" ]]; then
235    if cp "${WHL_DIR}/${WHL_BASE_NAME}" "${WHL_DIR}/${NEW_WHL_BASE_NAME}"
236    then
237      echo "Copied wheel file: ${WHL_BASE_NAME} --> ${NEW_WHL_BASE_NAME}"
238    else
239      die "ERROR: Failed to copy wheel file to ${NEW_WHL_BASE_NAME}"
240    fi
241  fi
242fi
243
244if [[ $(uname) == "Linux" ]]; then
245  AUDITED_WHL_NAME="${WHL_DIR}/$(echo ${WHL_BASE_NAME//linux/manylinux1})"
246
247  # Repair the wheels for cpu manylinux1
248  if [[ ${CONTAINER_TYPE} == "cpu" ]]; then
249    echo "auditwheel repairing ${WHL_PATH}"
250    auditwheel repair -w ${WHL_DIR} ${WHL_PATH}
251
252    if [[ -f ${AUDITED_WHL_NAME} ]]; then
253      WHL_PATH=${AUDITED_WHL_NAME}
254      echo "Repaired manylinx1 wheel file at: ${WHL_PATH}"
255    else
256      die "ERROR: Cannot find repaired wheel."
257    fi
258  # Copy and rename for gpu manylinux as we do not want auditwheel to package in libcudart.so
259  elif [[ ${CONTAINER_TYPE} == "gpu" ]] || \
260       [[ ${CONTAINER_TYPE} == "rocm" ]]; then
261    WHL_PATH=${AUDITED_WHL_NAME}
262    cp ${WHL_DIR}/${WHL_BASE_NAME} ${WHL_PATH}
263    echo "Copied manylinx1 wheel file at ${WHL_PATH}"
264  fi
265fi
266
267
268create_activate_virtualenv_and_install_tensorflow() {
269  # Create and activate a virtualenv; then install tensorflow pip package in it.
270  #
271  # Usage:
272  #   create_activate_virtualenv_and_install_tensorflow [--clean] \
273  #       <VIRTUALENV_DIR> <TF_WHEEL_PATH>
274  #
275  # Arguments:
276  #   --clean: Create a clean virtualenv, i.e., without --system-site-packages.
277  #   VIRTUALENV_DIR: virtualenv directory to be created.
278  #   TF_WHEEL_PATH: Path to the tensorflow wheel file to be installed in the
279  #     virtualenv.
280
281  VIRTUALENV_FLAGS="--system-site-packages"
282  if [[ "$1" == "--clean" ]]; then
283    VIRTUALENV_FLAGS=""
284    shift
285  fi
286
287  VIRTUALENV_DIR="$1"
288  TF_WHEEL_PATH="$2"
289  if [[ -d "${VIRTUALENV_DIR}" ]]; then
290    if rm -rf "${VIRTUALENV_DIR}"
291    then
292      echo "Removed existing virtualenv directory: ${VIRTUALENV_DIR}"
293    else
294      die "Failed to remove existing virtualenv directory: ${VIRTUALENV_DIR}"
295    fi
296  fi
297
298  if mkdir -p "${VIRTUALENV_DIR}"
299  then
300    echo "Created virtualenv directory: ${VIRTUALENV_DIR}"
301  else
302    die "FAILED to create virtualenv directory: ${VIRTUALENV_DIR}"
303  fi
304
305  # Use the virtualenv from the default python version (i.e., python-virtualenv)
306  # to create the virtualenv directory for testing. Use the -p flag to specify
307  # the python version inside the to-be-created virtualenv directory.
308  ${PYTHON_BIN_PATH} -m virtualenv -p "${PYTHON_BIN_PATH}" ${VIRTUALENV_FLAGS} \
309    "${VIRTUALENV_DIR}" || \
310    die "FAILED: Unable to create virtualenv"
311
312  source "${VIRTUALENV_DIR}/bin/activate" || \
313    die "FAILED: Unable to activate virtualenv in ${VIRTUALENV_DIR}"
314
315  # Install the pip file in virtual env.
316
317  # Upgrade pip so it supports tags such as cp27mu, manylinux1 etc.
318  echo "Upgrade pip in virtualenv"
319
320  # NOTE: pip install --upgrade pip leads to a documented TLS issue for
321  # some versions in python
322  curl https://bootstrap.pypa.io/get-pip.py | python
323
324  # Force upgrade of setuptools. This must happen before the pip install of the
325  # WHL_PATH, which pulls in absl-py, which uses install_requires notation
326  # introduced in setuptools >=20.5. The default version of setuptools is 5.5.1,
327  # which is too old for absl-py.
328  pip install --upgrade setuptools==39.1.0
329
330  # Force tensorflow reinstallation. Otherwise it may not get installed from
331  # last build if it had the same version number as previous build.
332  PIP_FLAGS="--upgrade --force-reinstall"
333  pip install -v ${PIP_FLAGS} ${WHL_PATH} || \
334    die "pip install (forcing to reinstall tensorflow) FAILED"
335  echo "Successfully installed pip package ${TF_WHEEL_PATH}"
336
337  # Force downgrade of setuptools. This must happen after the pip install of the
338  # WHL_PATH, which ends up upgrading to the latest version of setuptools.
339  # Versions of setuptools >= 39.1.0 will cause tests to fail like this:
340  #   ImportError: cannot import name py31compat
341  pip install --upgrade setuptools==39.1.0
342}
343
344################################################################################
345# Smoke test of tensorflow install in clean virtualenv
346################################################################################
347do_clean_virtualenv_smoke_test() {
348  if [[ -n "${NO_TEST_ON_INSTALL}" ]] &&
349       [[ "${NO_TEST_ON_INSTALL}" != "0" ]]; then
350    echo "NO_TEST_ON_INSTALL=${NO_TEST_ON_INSTALL}:"
351    echo "  Skipping smoke test of tensorflow install in clean virtualenv"
352    return ${SKIP_RETURN_CODE}
353  fi
354
355  CLEAN_VENV_DIR="${PIP_TEST_ROOT}/venv_clean"
356  create_activate_virtualenv_and_install_tensorflow --clean \
357    "${CLEAN_VENV_DIR}" "${WHL_PATH}"
358
359  # cd to a temporary directory to avoid picking up Python files in the source
360  # tree.
361  TMP_DIR=$(mktemp -d)
362  pushd "${TMP_DIR}"
363  if [[ $(python -c "import tensorflow as tf; print(tf.Session().run(tf.constant(42)))") == 42 ]];
364  then
365    echo "Smoke test of tensorflow install in clean virtualenv PASSED."
366  else
367    echo "Smoke test of tensorflow install in clean virtualenv FAILED."
368    return 1
369  fi
370
371  deactivate
372  if [[ $? != 0 ]]; then
373    echo "FAILED: Unable to deactivate virtualenv from ${CLEAN_VENV_DIR}"
374    return 1
375  fi
376
377  popd
378  rm -rf "${TMP_DIR}" "${CLEAN_VENV_DIR}"
379}
380
381################################################################################
382# Perform installation of tensorflow in "non-clean" virtualenv and tests against
383# the install.
384################################################################################
385do_virtualenv_pip_test() {
386  # Create virtualenv directory for install test
387  VENV_DIR="${PIP_TEST_ROOT}/venv"
388  create_activate_virtualenv_and_install_tensorflow \
389    "${VENV_DIR}" "${WHL_PATH}"
390
391  # Install extra pip packages required by the test-on-install
392  for PACKAGE in ${INSTALL_EXTRA_PIP_PACKAGES}; do
393    echo "Installing extra pip package required by test-on-install: ${PACKAGE}"
394
395    pip install ${PACKAGE}
396    if [[ $? != 0 ]]; then
397      echo "pip install ${PACKAGE} FAILED"
398      return 1
399    fi
400  done
401
402  if [[ -n "${NO_TEST_ON_INSTALL}" ]] &&
403     [[ "${NO_TEST_ON_INSTALL}" != "0" ]]; then
404    echo "NO_TEST_ON_INSTALL=${NO_TEST_ON_INSTALL}:"
405    echo "  Skipping ALL Python unit tests on install"
406    return ${SKIP_RETURN_CODE}
407  else
408    # Call run_pip_tests.sh to perform test-on-install
409    "${SCRIPT_DIR}/run_pip_tests.sh" --virtualenv ${GPU_FLAG} ${MAC_FLAG}
410    if [[ $? != 0 ]]; then
411      echo "PIP tests-on-install FAILED"
412      return 1
413    fi
414  fi
415}
416
417################################################################################
418# Run tests tagged with oss_serial against the virtualenv install.
419################################################################################
420do_virtualenv_oss_serial_pip_test() {
421  if [[ -n "${NO_TEST_ON_INSTALL}" ]] &&
422     [[ "${NO_TEST_ON_INSTALL}" != "0" ]]; then
423    echo "NO_TEST_ON_INSTALL=${NO_TEST_ON_INSTALL}:"
424    echo "  Skipping Python unit tests on install tagged with oss_serial"
425    return ${SKIP_RETURN_CODE}
426  else
427    # Call run_pip_tests.sh to perform test-on-install
428    "${SCRIPT_DIR}/run_pip_tests.sh" \
429      --virtualenv ${GPU_FLAG} ${MAC_FLAG} --oss_serial
430    if [[ $? != 0 ]]; then
431      echo "PIP tests-on-install (oss_serial) FAILED"
432      return 1
433    fi
434  fi
435}
436
437################################################################################
438# Test user ops (optional).
439################################################################################
440do_test_user_ops() {
441  if [[ "${DO_TEST_USER_OPS}" == "1" ]]; then
442    "${SCRIPT_DIR}/test_user_ops.sh" --virtualenv ${GPU_FLAG}
443    if [[ $? != 0 ]]; then
444      echo "PIP user-op tests-on-install FAILED"
445      return 1
446    fi
447  else
448    echo "Skipping user-op test-on-install due to DO_TEST_USER_OPS = ${DO_TEST_USER_OPS}"
449    return ${SKIP_RETURN_CODE}
450  fi
451}
452
453################################################################################
454# Test TensorFlow Debugger (tfdbg) binaries (optional).
455################################################################################
456do_test_tfdbg_binaries() {
457  if [[ "${DO_TEST_TFDBG_BINARIES}" == "1" ]]; then
458    # cd to a temporary directory to avoid picking up Python files in the source
459    # tree.
460    TMP_DIR=$(mktemp -d)
461    pushd "${TMP_DIR}"
462
463    "${SCRIPT_DIR}/../../../python/debug/examples/examples_test.sh" \
464      --virtualenv
465    if  [[ $? != 0 ]]; then
466      echo "PIP tests-on-install of tfdbg binaries FAILED"
467      return 1
468    fi
469    popd
470  else
471    echo "Skipping test of tfdbg binaries due to DO_TEST_TFDBG_BINARIES = ${DO_TEST_TFDBG_BINARIES}"
472    return ${SKIP_RETURN_CODE}
473  fi
474}
475
476################################################################################
477# Test tutorials (optional).
478################################################################################
479do_test_tutorials() {
480  if [[ "${DO_TEST_TUTORIALS}" == "1" ]]; then
481    "${SCRIPT_DIR}/test_tutorials.sh" --virtualenv
482    if [[ $? != 0 ]]; then
483      echo "PIP tutorial tests-on-install FAILED"
484      return 1
485    fi
486  else
487    echo "Skipping tutorial tests-on-install due to DO_TEST_TUTORIALS = ${DO_TEST_TUTORIALS}"
488    return ${SKIP_RETURN_CODE}
489  fi
490}
491
492################################################################################
493# Integration test for ffmpeg (optional).
494################################################################################
495do_ffmpeg_integration_test() {
496  # Optional: Run integration tests
497  if [[ "${DO_INTEGRATION_TESTS}" == "1" ]]; then
498    "${SCRIPT_DIR}/integration_tests.sh" --virtualenv
499    if [[ $? != 0 ]]; then
500      echo "Integration tests on install FAILED"
501      return 1
502    fi
503  else
504    echo "Skipping ffmpeg integration due to DO_INTEGRATION_TESTS = ${DO_INTEGRATION_TESTS}"
505    return ${SKIP_RETURN_CODE}
506  fi
507}
508
509
510# List of all PIP test tasks and their descriptions.
511PIP_TASKS=("do_clean_virtualenv_smoke_test" "do_virtualenv_pip_test" "do_virtualenv_oss_serial_pip_test" "do_test_user_ops" "do_test_tfdbg_binaries" "do_test_tutorials" "do_ffmpeg_integration_test")
512PIP_TASKS_DESC=("Smoke test of pip install in clean virtualenv" "PIP tests in virtualenv" "PIP test in virtualenv (tag: oss_serial)" "User ops test" "TensorFlow Debugger (tfdbg) binaries test" "Tutorials test" "ffmpeg integration test")
513
514
515# Execute all the PIP test steps.
516COUNTER=0
517FAIL_COUNTER=0
518PASS_COUNTER=0
519SKIP_COUNTER=0
520while [[ ${COUNTER} -lt "${#PIP_TASKS[@]}" ]]; do
521  INDEX=COUNTER
522  ((INDEX++))
523
524  echo
525  printf "${COLOR_BOLD}=== PIP test step ${INDEX} of ${#PIP_TASKS[@]}: "\
526"${PIP_TASKS[COUNTER]} (${PIP_TASKS_DESC[COUNTER]}) ===${COLOR_NC}"
527  echo
528
529  ${PIP_TASKS[COUNTER]}
530  RESULT=$?
531
532  if [[ ${RESULT} == ${SKIP_RETURN_CODE} ]]; then
533    ((SKIP_COUNTER++))
534  elif [[ ${RESULT} != "0" ]]; then
535    ((FAIL_COUNTER++))
536  else
537    ((PASS_COUNTER++))
538  fi
539
540  STEP_EXIT_CODES+=(${RESULT})
541
542  echo ""
543  ((COUNTER++))
544done
545
546deactivate || die "FAILED: Unable to deactivate virtualenv from ${VENV_DIR}"
547
548
549# Print summary of build results
550COUNTER=0
551echo "==== Summary of PIP test results ===="
552while [[ ${COUNTER} -lt "${#PIP_TASKS[@]}" ]]; do
553  INDEX=COUNTER
554  ((INDEX++))
555
556  echo "${INDEX}. ${PIP_TASKS[COUNTER]}: ${PIP_TASKS_DESC[COUNTER]}"
557  if [[ ${STEP_EXIT_CODES[COUNTER]} == ${SKIP_RETURN_CODE} ]]; then
558    printf "  ${COLOR_LIGHT_GRAY}SKIP${COLOR_NC}\n"
559  elif [[ ${STEP_EXIT_CODES[COUNTER]} == "0" ]]; then
560    printf "  ${COLOR_GREEN}PASS${COLOR_NC}\n"
561  else
562    printf "  ${COLOR_RED}FAIL${COLOR_NC}\n"
563  fi
564
565  ((COUNTER++))
566done
567
568echo
569echo "${SKIP_COUNTER} skipped; ${FAIL_COUNTER} failed; ${PASS_COUNTER} passed."
570
571echo
572if [[ ${FAIL_COUNTER} == "0" ]]; then
573  printf "PIP test ${COLOR_GREEN}PASSED${COLOR_NC}\n"
574else
575  printf "PIP test ${COLOR_RED}FAILED${COLOR_NC}\n"
576  exit 1
577fi
578