From f3ac41dcf36ae18765ed7bd5d1359bfd09b59207 Mon Sep 17 00:00:00 2001 From: Robert Haschke Date: Fri, 25 Jan 2019 23:21:37 +0100 Subject: [PATCH] rework travis utility functions - more modularization - travis_run_simple: no folding - travis_run_true: folding, timing, accept failures - travis_run: folding, timing, not accepting failures - travis_run_wait: +timeout - support hierarchical folds (with custom fold names and titles) - travis_wait() to wait for a background process to finish - limit travis_run_wait's timeout to estimated remaining Travis' build time - filter() and filter-out() utility functions - further extended unittests for all these utility functions --- .travis.yml | 2 +- check_clang_format.sh | 38 +++-- check_clang_tidy.sh | 47 +++--- travis.sh | 104 ++++++++------ travis_functions.sh | 35 +++++ unittest.sh | 222 ++++++++++++++++++++++++---- util.sh | 326 ++++++++++++++++++++++++++++-------------- 7 files changed, 549 insertions(+), 225 deletions(-) create mode 100644 travis_functions.sh diff --git a/.travis.yml b/.travis.yml index c404f22c..9ec225c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ env: # Test BEFORE_SCRIPT and ros vs. ros-shadow-fixed - ROS_DISTRO=kinetic ROS_REPO=ros BEFORE_SCRIPT="echo first && false; echo second > /dev/null; echo Testing on $ROS_DISTRO" - - ROS_DISTRO=kinetic ROS_REPO=ros-shadow-fixed BEFORE_DOCKER_SCRIPT="wget -nv www.google.com; exit 0" + - ROS_DISTRO=kinetic ROS_REPO=ros-shadow-fixed BEFORE_DOCKER_SCRIPT="wget -nv www.google.com; return 1" # Build MoveIt repo - ROS_DISTRO=melodic ROS_REPO=ros-shadow-fixed TEST=clang-tidy-fix diff --git a/check_clang_format.sh b/check_clang_format.sh index 7f8b3e5e..48fbaa8a 100644 --- a/check_clang_format.sh +++ b/check_clang_format.sh @@ -1,25 +1,33 @@ +# Software License Agreement - BSD License +# +# Author: Dave Coleman + +travis_fold start clang.format "Running clang-format check" +travis_run_simple --display "cd to repository source: $CI_SOURCE_PATH" cd $CI_SOURCE_PATH + # Install Dependencies travis_run apt-get -qq install -y clang-format-3.9 -# Change to source directory. -cd $CI_SOURCE_PATH - -# This directory can have its own .clang-format config file but if not, MoveIt's will be provided +# Ensure that a .clang-format config file is present, if not download from MoveIt if [ ! -f .clang-format ]; then travis_run wget -nv "https://raw.githubusercontent.com/ros-planning/moveit/$ROS_DISTRO-devel/.clang-format" fi # Run clang-format -travis_time_start moveit_ci.clang-format "Running clang-format" # start fold -find . -name '*.h' -or -name '*.hpp' -or -name '*.cpp' | xargs clang-format-3.9 -i -style=file -travis_time_end # end fold +cmd="find . -name '*.h' -or -name '*.hpp' -or -name '*.cpp' | xargs clang-format-3.9 -i -style=file" +travis_run --display "Running clang-format${ANSI_RESET}\\n$cmd" "$cmd" -# Make sure no changes have occured in repo -if ! git diff-index --quiet HEAD --; then - # changes - echo -e "\033[31;1mclang-format test failed: The following changes are required to comply to rules:\033[0m" - git --no-pager diff - exit 1 # error -fi +# Check for changes in repo +travis_have_fixes +result=$? + +# Finish fold before printing result summary +travis_fold end clang.format -echo -e "\033[32;1mPassed clang-format check\033[0m" +if [ $result -eq 1 ] ; then + echo -e "${ANSI_GREEN}Passed clang-format check${ANSI_RESET}" +else + echo -e "${ANSI_RED}clang-format check failed. Open fold for details.${ANSI_RESET}" + echo -e "Run the following command to fix these issues:\\n$cmd" + exit 2 +fi diff --git a/check_clang_tidy.sh b/check_clang_tidy.sh index 34ba3da7..9320d07f 100644 --- a/check_clang_tidy.sh +++ b/check_clang_tidy.sh @@ -1,14 +1,13 @@ -# Change to source directory. -pushd $CI_SOURCE_PATH > /dev/null +travis_fold start clang.tidy "Running clang-tidy check" +travis_run_impl --display "cd to repository source: $CI_SOURCE_PATH" cd $CI_SOURCE_PATH # Find run-clang-tidy script: Xenial and Bionic install them with different names export RUN_CLANG_TIDY=$(ls -1 /usr/bin/run-clang-tidy* | head -1) - # Run clang-tidy for all packages in CI_SOURCE_PATH SOURCE_PKGS=$(catkin_topological_order $CI_SOURCE_PATH --only-names 2> /dev/null) -echo -e "\033[33;1mRunning clang-tidy check\033[0m" +TAINTED_PKGS="" COUNTER=0 ( for file in $(find $CATKIN_WS/build -name compile_commands.json) ; do @@ -17,31 +16,23 @@ COUNTER=0 [[ "$SOURCE_PKGS" =~ (^|[[:space:]])$PKG($|[[:space:]]) ]] && continue let "COUNTER += 1" - travis_time_start clang-tidy.$COUNTER "Processing $PKG" - # Pipe the very verbose output of clang-tidy to /dev/null - $RUN_CLANG_TIDY -fix -p $(dirname $file) > /dev/null 2>&1 - travis_time_end - done -) & -cmd_pid=$! # main cmd PID - -# Use travis_jigger to generate some '.' outputs to convince Travis, we are not stuck. -travis_jigger $cmd_pid 10 "clang-tidy" & -jigger_pid=$! + travis_fold start clang.tidy.$COUNTER "${ANSI_THIN}Processing $PKG" -# Wait for main command to finish -wait $cmd_pid 2>/dev/null -# Stop travis_jigger in any case -kill $jigger_pid 2> /dev/null && wait $! 2> /dev/null + cmd="$RUN_CLANG_TIDY -fix -p $(dirname $file)" + # Suppress the very verbose output of clang-tidy! + travis_run_impl --timing --display "$cmd" "$cmd > /dev/null 2>&1" + travis_have_fixes && TAINTED_PKGS="$TAINTED_PKGS\\n$PKG" + travis_fold end clang.tidy.$COUNTER + done +) & # run in background to allow timeout monitoring +travis_wait $! $(travis_timeout) +# Finish fold before printing result summary +travis_fold end clang.tidy -# Make sure no changes have occured in repo -if ! git diff-index --quiet HEAD --; then - # changes - echo -e "\033[31;1mclang-tidy test failed: The following changes are required to comply to rules:\033[0m" - git --no-pager diff - exit 1 +if [ -z "$TAINTED_PKGS" ] ; then + echo -e "${ANSI_GREEN}Passed clang-tidy check${ANSI_RESET}" +else + echo -e "${ANSI_RED}clang-tidy check failed for the following packages:\\n${ANSI_RESET}$TAINTED_PKGS" + exit 2 fi - -echo -e "\033[32;1mPassed clang-tidy check\033[0m" -popd > /dev/null diff --git a/travis.sh b/travis.sh index 275e1e66..d7e4cd7d 100755 --- a/travis.sh +++ b/travis.sh @@ -14,17 +14,21 @@ export CI_SOURCE_PATH=$(pwd) # The repository code in this pull request that we export CI_PARENT_DIR=.moveit_ci # This is the folder name that is used in downstream repositories in order to point to this repo. export REPOSITORY_NAME=${PWD##*/} export CATKIN_WS=/root/ws_moveit -echo "---" -echo -e "\033[33;1mTesting branch '$TRAVIS_BRANCH' of '$REPOSITORY_NAME' on ROS '$ROS_DISTRO'\033[0m" +# Travis' default timeout for open source projects is 50 mins +# If your project has a larger timeout, specify this variable in your .travis.yml file! +MOVEIT_CI_TRAVIS_TIMEOUT=${MOVEIT_CI_TRAVIS_TIMEOUT:-45} # 50min minus safety margin # Helper functions source ${CI_SOURCE_PATH}/$CI_PARENT_DIR/util.sh # Run all CI in a Docker container if ! [ "$IN_DOCKER" ]; then + echo -e "${ANSI_YELLOW}Testing branch '$TRAVIS_BRANCH' of '$REPOSITORY_NAME' on ROS '$ROS_DISTRO'${ANSI_RESET}" # Run BEFORE_DOCKER_SCRIPT if [ "${BEFORE_DOCKER_SCRIPT// }" != "" ]; then - travis_run $BEFORE_DOCKER_SCRIPT + travis_run --title "${ANSI_BOLD}Running BEFORE_DOCKER_SCRIPT${ANSI_THIN}" $BEFORE_DOCKER_SCRIPT + result=$? + test $result -ne 0 && echo -e "${ANSI_RED}Script failed with return value: $result. Aborting.${ANSI_RESET}" && exit 2 fi # Choose the correct CI container to use @@ -36,14 +40,14 @@ if ! [ "$IN_DOCKER" ]; then export DOCKER_IMAGE=moveit/moveit:$ROS_DISTRO-ci ;; esac - echo "Starting Docker image: $DOCKER_IMAGE" + echo -e "${ANSI_BOLD}Starting Docker image: $DOCKER_IMAGE${ANSI_RESET}" - # Pull first to allow us to hide console output - docker pull $DOCKER_IMAGE > /dev/null + travis_run docker pull $DOCKER_IMAGE # Start Docker container docker run \ -e TRAVIS \ + -e MOVEIT_CI_TRAVIS_TIMEOUT \ -e ROS_REPO \ -e ROS_DISTRO \ -e BEFORE_SCRIPT \ @@ -64,11 +68,12 @@ if ! [ "$IN_DOCKER" ]; then /bin/bash -c "cd /root/$REPOSITORY_NAME; source .moveit_ci/travis.sh;" return_value=$? - if [ $return_value -eq 0 ]; then - echo -e "\033[32;1mTravis script finished successfully\033[0m" - else - echo -e "\033[31;1mTravis script finished with errors\033[0m" - fi + echo + case $return_value in + 0) echo -e "${ANSI_GREEN}Travis script finished successfully.${ANSI_RESET}" ;; + 124) echo -e "${ANSI_YELLOW}Timed out, but try again! Having saved cache results, Travis will probably succeed next time.${ANSI_RESET}\\n" ;; + *) echo -e "${ANSI_RED}Travis script finished with errors.${ANSI_RESET}" ;; + esac exit $return_value fi @@ -78,8 +83,9 @@ echo "Inside Docker container" # Define CC/CXX defaults and print compiler version info export CC=${CC:-cc} export CXX=${CXX:-c++} -$CXX --version +travis_run --title "${ANSI_RESET}$CXX compiler info" $CXX --version +travis_fold start update "Updating system packages" # Update the sources travis_run apt-get -qq update @@ -90,6 +96,7 @@ travis_run apt-get -qq dist-upgrade for t in $TEST; do case "$t" in clang-format) + travis_fold end update source ${CI_SOURCE_PATH}/$CI_PARENT_DIR/check_clang_format.sh || exit 1 exit 0 # This runs as an independent job, do not run regular travis test ;; @@ -106,7 +113,7 @@ for t in $TEST; do CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_EXPORT_COMPILE_COMMANDS=ON" ;; *) - echo "Unknown TEST: $t" + echo -e "${ANSI_RED}Unknown TEST: $t${ANSI_RESET}" exit 1 ;; esac @@ -122,18 +129,23 @@ fi travis_run apt-get -qq install ccache export PATH=/usr/lib/ccache:$PATH +# Setup rosdep - note: "rosdep init" is already setup in base ROS Docker image +travis_run rosdep update + +travis_fold end update + # Install and run xvfb to allow for X11-based unittests on DISPLAY :99 +travis_fold start xvfb "Starting virtual X11 server to allow for X11-based unit tests" travis_run apt-get -qq install xvfb mesa-utils -Xvfb -screen 0 640x480x24 :99 & +travis_run "Xvfb -screen 0 640x480x24 :99 &" export DISPLAY=:99.0 travis_run_true glxinfo - -# Setup rosdep - note: "rosdep init" is already setup in base ROS Docker image -travis_run rosdep update +travis_fold end xvfb # Create workspace -travis_run mkdir -p $CATKIN_WS/src -travis_run cd $CATKIN_WS/src +travis_fold start catkin.ws "Setting up catkin workspace" +travis_run_simple mkdir -p $CATKIN_WS/src +travis_run_simple cd $CATKIN_WS/src # Install dependencies necessary to run build using .rosinstall files if [ ! "$UPSTREAM_WORKSPACE" ]; then @@ -149,7 +161,7 @@ case "$UPSTREAM_WORKSPACE" in ( # parentheses ensure that IFS is automatically reset IFS=',' # Multiple URLs can be given separated by comma. for rosinstall in $UPSTREAM_WORKSPACE; do - travis_run wstool merge -k $rosinstall + (IFS=' '; travis_run wstool merge -k $rosinstall) done ) ;; @@ -159,8 +171,8 @@ case "$UPSTREAM_WORKSPACE" in # install (maybe unreleased version) dependencies from source travis_run wstool merge file://$CI_SOURCE_PATH/$UPSTREAM_WORKSPACE else - echo "Didn't find rosinstall file: $CI_SOURCE_PATH/$UPSTREAM_WORKSPACE. Aborting" - exit 1 + echo -e "${ANSI_RED}Didn't find rosinstall file: $CI_SOURCE_PATH/$UPSTREAM_WORKSPACE. Aborting.${ANSI_RESET}" + exit 2 fi ;; esac @@ -170,67 +182,67 @@ if [ -e .rosinstall ]; then # ensure that the downstream is not in .rosinstall travis_run_true wstool rm $REPOSITORY_NAME # perform shallow checkout: only possible with wstool init - travis_run mv .rosinstall rosinstall + travis_run_simple mv .rosinstall rosinstall travis_run cat rosinstall travis_run wstool init --shallow . rosinstall fi # link in the repo we are testing -travis_run ln -s $CI_SOURCE_PATH . +travis_run_simple --title "${ANSI_RESET}Symlinking to-be-tested repo $CI_SOURCE_PATH into catkin workspace" \ + ln -s $CI_SOURCE_PATH . # Debug: see the files in current folder -travis_run ls -a +travis_run --title "${ANSI_RESET}List files catkin workspace's source folder" ls -a # Run BEFORE_SCRIPT if [ "${BEFORE_SCRIPT// }" != "" ]; then - travis_run $BEFORE_SCRIPT + travis_run --title "${ANSI_BOLD}Running BEFORE_SCRIPT${ANSI_THIN}" $BEFORE_SCRIPT + result=$? + test $result -ne 0 && echo -e "${ANSI_RED}Script failed with return value: $result. Aborting.${ANSI_RESET}" && exit 2 fi # Install source-based package dependencies travis_run rosdep install -y -q -n --from-paths . --ignore-src --rosdistro $ROS_DISTRO # Change to base of workspace -travis_run cd $CATKIN_WS +travis_run_simple cd $CATKIN_WS +travis_fold end catkin.ws -echo -e "\033[33;1mBuilding Workspace\033[0m" +echo -e "${ANSI_GREEN}Building Workspace${ANSI_RESET}" # Configure catkin -travis_run catkin config --extend /opt/ros/$ROS_DISTRO --install --cmake-args -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS_RELEASE="-O3" $CMAKE_ARGS -- +travis_run --title "catkin config $CMAKE_ARGS" catkin config --extend /opt/ros/$ROS_DISTRO --install --cmake-args -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS_RELEASE="-O3" $CMAKE_ARGS -- # Console output fix for: "WARNING: Could not encode unicode characters" export PYTHONIOENCODING=UTF-8 # For a command that doesn’t produce output for more than 10 minutes, prefix it with travis_run_wait -travis_run_wait 60 catkin build --no-status --summarize || exit 1 +travis_run_wait 60 --title "catkin build" catkin build --no-status --summarize -travis_run ccache -s +travis_run --title "ccache statistics" ccache -s -# Source the new built workspace -travis_run source install/setup.bash; +echo -e "${ANSI_GREEN}Testing Workspace${ANSI_RESET}" +travis_run_simple --title "Sourcing newly built install space" source install/setup.bash # Choose which packages to run tests on -echo -e "\033[33;1mTesting Workspace\033[0m" echo "Test blacklist: $TEST_BLACKLIST" -TEST_PKGS=$(catkin_topological_order $CATKIN_WS/src --only-names 2> /dev/null | grep -Fvxf <(echo "$TEST_BLACKLIST" | tr ' ;,' '\n') | tr '\n' ' ') +TEST_PKGS=$(filter-out "$TEST_BLACKLIST" $(catkin_topological_order $CATKIN_WS/src --only-names)) if [ -n "$TEST_PKGS" ]; then - # Fix formatting of list of packages to work correctly with Travis - IFS=' ' read -r -a TEST_PKGS <<< "$TEST_PKGS" - echo "Test packages: ${TEST_PKGS[@]}" - TEST_PKGS="--no-deps ${TEST_PKGS[@]}" - - # Run catkin package tests - travis_run_wait catkin build --no-status --summarize --make-args tests -- $TEST_PKGS + echo "Test packages: $TEST_PKGS" + TEST_PKGS="--no-deps $TEST_PKGS" - # Run non-catkin package tests - travis_run_wait catkin build --catkin-make-args run_tests -- --no-status --summarize $TEST_PKGS + travis_run_wait --title "catkin build tests" catkin build --no-status --summarize --make-args tests -- $TEST_PKGS + travis_run_wait --title "catkin run_tests" catkin build --catkin-make-args run_tests -- --no-status --summarize $TEST_PKGS # Show failed tests + travis_fold start test.results "catkin_test_results" for file in $(catkin_test_results | grep "\.xml:" | cut -d ":" -f1); do - travis_run cat $file + travis_run --display "Test log of $file" cat $file done + travis_fold end test.results # Show test results summary and throw error if necessary - travis_run catkin_test_results + catkin_test_results || exit 2 else echo "No packages to test." fi diff --git a/travis_functions.sh b/travis_functions.sh new file mode 100644 index 00000000..707baa1a --- /dev/null +++ b/travis_functions.sh @@ -0,0 +1,35 @@ +# from https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/bash/travis_cmd.bash + +export ANSI_RED="\033[31;1m" +export ANSI_GREEN="\033[32;1m" +export ANSI_YELLOW="\033[33;1m" +export ANSI_RESET="\033[0m" +export ANSI_CLEAR="\033[0K" + +travis_nanoseconds() { + local cmd='date' + local format='+%s%N' + + if hash gdate >/dev/null 2>&1; then + cmd='gdate' + elif [[ "${TRAVIS_OS_NAME}" == osx ]]; then + format='+%s000000000' + fi + + "${cmd}" -u "${format}" +} +travis_time_start() { + TRAVIS_TIMER_ID="$(printf %08x $((RANDOM * RANDOM)))" + TRAVIS_TIMER_START_TIME="$(travis_nanoseconds)" + export TRAVIS_TIMER_ID TRAVIS_TIMER_START_TIME + echo -en "travis_time:start:$TRAVIS_TIMER_ID\\r${ANSI_CLEAR}" +} +travis_time_finish() { + local result="${?}" + local travis_timer_end_time + travis_timer_end_time="$(travis_nanoseconds)" + local duration + duration="$((travis_timer_end_time - TRAVIS_TIMER_START_TIME))" + echo -en "travis_time:end:${TRAVIS_TIMER_ID}:start=${TRAVIS_TIMER_START_TIME},finish=${travis_timer_end_time},duration=${duration}\\r${ANSI_CLEAR}" + return "${result}" +} diff --git a/unittest.sh b/unittest.sh index 24f55da1..57347c94 100755 --- a/unittest.sh +++ b/unittest.sh @@ -1,62 +1,234 @@ #!/bin/bash +#******************************************************************** +# Software License Agreement (BSD License) +# +# Copyright (c) 2018, Bielefeld University +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Bielefeld University nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +#********************************************************************/ + +# Author: Robert Haschke +# Desc: unittests for travis_* functions in util.sh + source util.sh -# save stdout as 3 and stderr as 4, then redirect stdout(1) to /dev/null and stderr(2) to 3(stdout) +# To suppress normal Travis output: +# - save stdout as 3 and stderr as 4, +# - then redirect stdout(1) to /dev/null and stderr(2) to 3(stdout) exec 3>&1 4>&2 1>/dev/null 2>&3 +function debug { # use instead of "echo ..." + 1>&3 echo -e "$*" +} +PASSES=0 +FAILED=0 function EXPECT_TRUE { local test_cmd=$1 local location=$2 - local message=$3 + local message="$3 " + local type=${4:-"Expectation"} if ! eval $test_cmd ; then + 1>&3 echo -e "${ANSI_RED}${type} failed: ${ANSI_RESET}${test_cmd}\\n${message% }($location)" + let "FAILED += 1" + return 1 + else + let "PASSES += 1" + fi +} +function ASSERT_TRUE { + if ! EXPECT_TRUE "$@" "Assertion"; then exec 1>&3 2>&4 # restore stdout + stderr - echo -e "$location:\033[31;1m $test_cmd \033[0m $message" - exit -1 + echo -e "${ANSI_RED}Terminating.${ANSI_RESET}" + exit 2 fi } +function strip_off_ansi_codes { + echo -e $* | sed -e 's:\x1B\[[0-9;]*[mK]::g' -e 's:[[:cntrl:]]::g' +} + +# Testing strip_off_ansi_codes +output=$(strip_off_ansi_codes "${ANSI_RED}foo\\r\\t${ANSI_RESET}bar${ANSI_CLEAN}") +ASSERT_TRUE "test \"$output\" == \"foobar\"" $0:$LINENO "Should strip all ansi codes, tabs, and carriage return chars" + + +# signatures of start / end timing tag +TIMING_START="travis_time:start:[[:xdigit:]]+" +TIMING_END="travis_time:end:[[:xdigit:]]+:start=[[:digit:]]+,finish=[[:digit:]]+,duration=[[:digit:]]+" +# signatures of start / end folding tag +FOLDING_START="travis_fold:start:moveit_ci\." +FOLDING_END="travis_fold:end:moveit_ci\." + +# Testing travis_fold +output=$(strip_off_ansi_codes $(travis_fold start; travis_fold end)) +EXPECT_TRUE "[[ \"$output\" =~ ^${FOLDING_START}1${FOLDING_END}1$ ]]" $0:$LINENO "fold tag generation fails" +output=$(strip_off_ansi_codes $(travis_fold start; travis_fold start)) +EXPECT_TRUE "[[ \"$output\" =~ ^${FOLDING_START}1${FOLDING_START}2$ ]]" $0:$LINENO "Expecting number to increase" + +output=$(strip_off_ansi_codes $(travis_fold start name; travis_fold end name)) +EXPECT_TRUE "[[ \"$output\" =~ ^travis_fold:start:name\.1travis_fold:end:name\.1$ ]]" $0:$LINENO "fold tag generation fails" + +output=$(travis_fold start name; travis_fold end other) +EXPECT_TRUE "test $? -eq 1" $0:$LINENO "Expecting failure due to mismatching fold names" +EXPECT_TRUE "[[ \"$(strip_off_ansi_codes $output)\" == *match* ]]" $0:$LINENO "Expecting error message mentioning mismatch" + +output=$(travis_fold end other) +EXPECT_TRUE "test $? -eq 1" $0:$LINENO "Expecting failure due to missing start fold" +EXPECT_TRUE "[[ \"$(strip_off_ansi_codes $output)\" == *issing* ]]" $0:$LINENO "Expecting error message mentioning missing start" + +output=$(strip_off_ansi_codes $(travis_fold start name message)) +EXPECT_TRUE "[[ \"$output\" =~ ^travis_fold:start:name\.1message$ ]]" $0:$LINENO "For start action, message should be shown" + +output=$(strip_off_ansi_codes $(travis_fold start name; travis_fold end name message)) +EXPECT_TRUE "[[ \"$output\" =~ ^travis_fold:start:name\.1travis_fold:end:name\.1$ ]]" $0:$LINENO "For end action, message should be suppressed" + + +# Testing travis_timeout +output=$(travis_timeout) +EXPECT_TRUE "test $? -eq 1" $0:$LINENO "Expecting result 1, because no timeout parameter was given" +EXPECT_TRUE "test \"$output\" == \"20\"" $0:$LINENO "Expecting default timeout" + +output=$(travis_timeout "foo") +EXPECT_TRUE "test $? -eq 1" $0:$LINENO "Expecting result 1, because no valid timeout was given" +EXPECT_TRUE "test \"$output\" == \"20\"" $0:$LINENO "Expecting default timeout" + +output=$(travis_timeout 42) +EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Expecting result 0, because a valid timeout was given" +EXPECT_TRUE "test \"$output\" == \"42\"" $0:$LINENO "Expecting given timeout" + +output=$(MOVEIT_CI_TRAVIS_TIMEOUT=0; travis_timeout 1) +EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Expecting result 0, because a valid timeout was given" +EXPECT_TRUE "test \"$output\" == \"0\"" $0:$LINENO "Expecting zero timeout" + -# travis_run_impl should return the value of the (last) command +# travis_run_impl should return the return value of the (last) command travis_run_impl true -EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Wrong result" +EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Expecting success" travis_run_impl false -EXPECT_TRUE "test $? -eq 1" $0:$LINENO "Wrong result" +EXPECT_TRUE "test $? -eq 1" $0:$LINENO "Expecting failure" +(travis_run_simple false) # run in subshell to avoid exit from this script +EXPECT_TRUE "test $? -eq 2" $0:$LINENO "Expecting terminate" -travis_run_impl return 2 +travis_run_impl "(return 2)" EXPECT_TRUE "test $? -eq 2" $0:$LINENO "Wrong result" - -# several commands should be possible too, e.g. when passed into BEFORE_SCRIPT variable -cmds="echo; return 2" -travis_run_impl $cmds -EXPECT_TRUE "test $? -eq 2" $0:$LINENO "Wrong result" - +(travis_run_simple "(return 4)") # run in subshell to avoid exit from this script +EXPECT_TRUE "test $? -eq 2" $0:$LINENO "Expecting terminate" + +output=$(strip_off_ansi_codes $(var=bar; travis_run_impl --hide "echo -n foo; echo -n $var")) +EXPECT_TRUE "test \"$output\" == \"foobar\"" $0:$LINENO "Expecting output from two echos" +output=$(strip_off_ansi_codes $(travis_run_impl --timing --display "message" "true")) +EXPECT_TRUE "[[ \"$output\" =~ ^${TIMING_START}message\ ${TIMING_END}$ ]]" $0:$LINENO "Invalid timing message" +output=$(strip_off_ansi_codes $(travis_run_impl --timing --display "message" "echo foo &>/dev/null")) +EXPECT_TRUE "[[ \"$output\" =~ ^${TIMING_START}message\ ${TIMING_END}$ ]]" $0:$LINENO "Unexpected cmd output" +output=$(strip_off_ansi_codes $(travis_run_impl true)) +EXPECT_TRUE "test \"$output\" == \"true\"" $0:$LINENO "Expecting cmd to be echoed" +output=$(strip_off_ansi_codes $(travis_run_impl --hide true)) +EXPECT_TRUE "test \"$output\" == \"\"" $0:$LINENO "--hide should suppress echo" +output=$(strip_off_ansi_codes $(travis_run_impl --display "me ss age" echo -n con tent)) +EXPECT_TRUE "test \"$output\" == \"me ss age con tent\"" $0:$LINENO "Expecting custom message" # travis_run should continue on success travis_run true -EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Wrong result" +EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Expecting success" # ... but exit on failure (travis_run false) # run in subshell to avoid exit from this script -EXPECT_TRUE "test $? -eq 1" $0:$LINENO "Wrong result" +EXPECT_TRUE "test $? -eq 2" $0:$LINENO "Expecting terminate" + +# Validate multi-token commands with multi-token custom message +# signatures of start / end tokens +TOKEN_START="${FOLDING_START}2${TIMING_START}" +TOKEN_END="${TIMING_END}${FOLDING_END}2" +output=$(strip_off_ansi_codes $(travis_run --display "me ss age" echo con tent)) +EXPECT_TRUE "[[ \"$output\" =~ ^${TOKEN_START}me\ ss\ age\ con\ tent\ ${TOKEN_END}$ ]]" $0:$LINENO "Expecting custom message" +output=$(strip_off_ansi_codes $(travis_run_true --display "me ss age" echo con tent)) +EXPECT_TRUE "[[ \"$output\" =~ ^${TOKEN_START}me\ ss\ age\ con\ tent\ ${TOKEN_END}$ ]]" $0:$LINENO "Expecting custom message" +# Running $(travis_run_wait ...) as a subshell, sleeps for 60s in travis_monitor for no reason +#output=$(strip_off_ansi_codes $(travis_run_wait 10 --display "me ss age" echo con tent)) +#EXPECT_TRUE "[[ \"$output\" =~ ^${TOKEN_START}me\ ss\ age\ con\ tent\ ${TOKEN_END}$ ]]" $0:$LINENO "Expecting custom message" + +# Redirection needs to be embedded into the command +output=$(strip_off_ansi_codes $(travis_run --display "me ss age" "echo con tent > /dev/null")) +EXPECT_TRUE "[[ \"$output\" =~ ^${TOKEN_START}me\ ss\ age\ ${TOKEN_END}$ ]]" $0:$LINENO "Redirection not working" # travis_run_wait should continue on success travis_run_wait 10 true -EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Wrong result" +EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Expecting success" -# ... but exit on failure +# first (numerical) argument to travis_run_wait is optional +travis_run_wait true +EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Expecting success" + +# On timeout, travis_run_wait should return with code 124 +travis_run_wait 0 sleep 100 +EXPECT_TRUE "test $? -eq 124" $0:$LINENO "Expecting timeout" + +# travis_run_wait should exit with code 2 on failure (travis_run_wait 10 false) # run in subshell to avoid exit from this script -EXPECT_TRUE "test $? -eq 1" $0:$LINENO "Wrong result" +EXPECT_TRUE "test $? -eq 2" $0:$LINENO "Expecting terminate" -# first (numerical) argument to travis_run_wait is optional -travis_run_wait true -EXPECT_TRUE "test $? -eq 0" $0:$LINENO "Wrong result" +# test filter +output=$(filter "t2 t4" $(echo t1 t2 t3 t4)) +EXPECT_TRUE "test \"${output% *}\" == \"t2 t4\"" $0:$LINENO "" + +output=$(filter "" "t1,t2,t3,t4") +EXPECT_TRUE "test \"${output% *}\" == \"\"" $0:$LINENO "" + +output=$(filter "t1;" "t1;t2;t3;t4") +EXPECT_TRUE "test \"${output% *}\" == \"t1\"" $0:$LINENO "" + +output=$(filter "missing" "t1;t2;t3;t4") +EXPECT_TRUE "test \"${output% *}\" == \"\"" $0:$LINENO "" -(travis_run_wait false) # run in subshell to avoid exit from this script -EXPECT_TRUE "test $? -eq 1" $0:$LINENO "Wrong result" +# test filter-out +output=$(filter-out "t2 t4" $(echo t1 t2 t3 t4)) +EXPECT_TRUE "test \"${output% *}\" == \"t1 t3\"" $0:$LINENO "" + +output=$(filter-out "" "t1,t2,t3,t4") +EXPECT_TRUE "test \"${output% *}\" == \"t1 t2 t3 t4\"" $0:$LINENO "" + +output=$(filter-out "t1;" "t1;t2;t3;t4") +EXPECT_TRUE "test \"${output% *}\" == \"t2 t3 t4\"" $0:$LINENO "" + +output=$(filter-out "missing" "t1;t2;t3;t4") +EXPECT_TRUE "test \"${output% *}\" == \"t1 t2 t3 t4\"" $0:$LINENO "" exec 1>&3 2>&4 # restore stdout + stderr -echo -e "\033[32;1mSuccessfully passed unittest\033[0m" + +ALL=$(($PASSES + $FAILED)) +if [ $FAILED -ne 0 ] ; then + echo -e "${ANSI_RED}$FAILED out of $ALL tests failed. Terminating.${ANSI_RESET}" + exit 2 +else + echo -e "${ANSI_GREEN}${ANSI_THIN}Successfully passed all $ALL tests${ANSI_RESET}" +fi diff --git a/util.sh b/util.sh index 3e069f94..7ca9afc0 100644 --- a/util.sh +++ b/util.sh @@ -1,8 +1,7 @@ -#!/bin/bash #******************************************************************** # Software License Agreement (BSD License) # -# Copyright (c) 2016, University of Colorado, Boulder +# Copyright (c) 2018, Bielefeld University # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -15,7 +14,7 @@ # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. -# * Neither the name of the Univ of CO, Boulder nor the names of its +# * Neither the name of Bielefeld University nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # @@ -33,141 +32,248 @@ # POSSIBILITY OF SUCH DAMAGE. #********************************************************************/ -# Author: Dave Coleman , Robert Haschke -# Desc: Utility functions used to make CI work better in Travis +# Author: Robert Haschke, Dave Coleman +# Desc: Utility functions to facilitate writing job scripts +# Strongly builds on corresponding bash utility functions of travis-ci: +# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/bash -####################################### -export TRAVIS_FOLD_COUNTER=0 +# source default functions, copied from travis-ci project +source travis_functions.sh +export ANSI_BLUE="\033[34;1m" +export ANSI_THIN="\033[22m" +export ANSI_BOLD="\033[1m" -####################################### -# Start a Travis fold with timer -# -# Arguments: -# travis_fold_name: name of line -# command: action to run -####################################### -function travis_time_start { - TRAVIS_START_TIME=$(date +%s%N) - TRAVIS_TIME_ID=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 8 | head -n 1) - TRAVIS_FOLD_NAME=$1 - local COMMAND=${@:2} # all arguments except the first - - # Start fold - echo -e "\e[0Ktravis_fold:start:$TRAVIS_FOLD_NAME" - # Output command being executed - echo -e "\e[0Ktravis_time:start:$TRAVIS_TIME_ID\e[34m$COMMAND\e[0m" -} +# set start time (if not yet done) +MOVEIT_CI_START_TIME=${MOVEIT_CI_START_TIME:-$(travis_nanoseconds)} -####################################### -# Wraps up the timer section on Travis CI (that's started mostly by travis_time_start function). -# -# Arguments: -# travis_fold_name: name of line -####################################### -function travis_time_end { - if [ -z $TRAVIS_START_TIME ]; then - echo '[travis_time_end] var TRAVIS_START_TIME is not set. You need to call `travis_time_start` in advance.'; - return; - fi - local TRAVIS_END_TIME=$(date +%s%N) - local TIME_ELAPSED_SECONDS=$(( ($TRAVIS_END_TIME - $TRAVIS_START_TIME)/1000000000 )) - - # Output Time - echo -e "travis_time:end:$TRAVIS_TIME_ID:start=$TRAVIS_START_TIME,finish=$TRAVIS_END_TIME,duration=$(($TRAVIS_END_TIME - $TRAVIS_START_TIME))\e[0K" - # End fold - echo -e -n "travis_fold:end:$TRAVIS_FOLD_NAME\e[0m" - - unset TRAVIS_START_TIME - unset TRAVIS_TIME_ID - unset TRAVIS_FOLD_NAME -} +# Output the smaller one of the optional timeout parameter ($1) or the remaining time (in minutes) +# Return 0 if optional timeout parameter was consumed (looking like an integer), 1 if not +# This allows to compute the actual timeout in functions like this: +# timeout=$(travis_timeout "$1") && shift +travis_timeout() { + local timeout result remaining + if [[ "${timeout:=$1}" =~ ^[0-9]+$ ]] ; then + result=0 # $1 looks like integer that should be consumed as a parameter + else + result=1 # parameter shouldn't be consumed + timeout=20 # default timeout + fi -####################################### -# Display command in Travis console and fold output in dropdown section -# -# Arguments: commands to run -# Return: exit status of the command -####################################### -function travis_run_impl() { - local commands=$@ - - let "TRAVIS_FOLD_COUNTER += 1" - travis_time_start moveit_ci.$TRAVIS_FOLD_COUNTER $commands - # actually run commands, eval needed to handle multiple commands! - eval $commands - result=$? - travis_time_end + # remaining time in minutes (default timeout for open-source Travis jobs is 50min) + remaining=$(( ${MOVEIT_CI_TRAVIS_TIMEOUT:-50} - ($(travis_nanoseconds) - $MOVEIT_CI_START_TIME) / 60000000000 )) + # limit timeout to remaining time + if [ $remaining -le $timeout ] ; then timeout=$remaining; fi + echo "${timeout}" return $result } -####################################### -# Run passed commands and exit if the last one fails -function travis_run() { - travis_run_impl $@ || exit $? -} +# travis_fold (start|end) [name] [message] +travis_fold() { + # option -g declares those arrays globally! + declare -ag _TRAVIS_FOLD_NAME_STACK # "stack" array to hold name hierarchy + declare -Ag _TRAVIS_FOLD_COUNTERS # associated array to hold global counters + + local action="$1" + local name="${2:-moveit_ci}" # name defaults to moveit_ci + name="${name/ /.}" # replace spaces with dots in name + local message="$3" + test -n "$message" && message="${ANSI_BLUE}$3${ANSI_RESET}\\n" # print message in bold blue by default -####################################### -# Same as travis_run but ignore any error -function travis_run_true() { - travis_run_impl $@ || true + local length=${#_TRAVIS_FOLD_NAME_STACK[@]} + if [ "$action" == "start" ] ; then + # push name to stack + _TRAVIS_FOLD_NAME_STACK[$length]=$name + # increment (or initialize) matching counter + let "_TRAVIS_FOLD_COUNTERS[$name]=${_TRAVIS_FOLD_COUNTERS[$name]:=0} + 1" + else + action="end" + message="" # only start action may have a message + # pop name from stack + let "length -= 1" + test $length -lt 0 && \ + echo -e "Missing travis_fold start before travis_fold end $name" && exit 1 + test "${_TRAVIS_FOLD_NAME_STACK[$length]}" != "$name" && \ + echo "'travis_fold end $name' not matching to previous travis_fold start ${_TRAVIS_FOLD_NAME_STACK[$length]}" && exit 1 + unset '_TRAVIS_FOLD_NAME_STACK[$length]' + fi + # actually generate the fold tag for travis + echo -en "travis_fold:${action}:${name}.${_TRAVIS_FOLD_COUNTERS[$name]}\\r${ANSI_CLEAR}${message}" } -####################################### -# Same as travis_run, but issue some output regularly to indicate that the process is still alive -# from: https://github.com/travis-ci/travis-build/blob/d63c9e95d6a2dc51ef44d2a1d96d4d15f8640f22/lib/travis/build/script/templates/header.sh -function travis_run_wait() { - local timeout=$1 # in minutes - if [[ $timeout =~ ^[0-9]+$ ]]; then - # looks like an integer, so we assume it's a timeout +# Run a command in Travis with nice folding display, timing, timeout etc. +# adapted from travis_cmd.bash +travis_run_impl() { + local assert hide title display timing timeout cmd result + + while true; do + case "${1}" in + --assert) # terminate on failure? + assert=true + ;; + --hide) # hide cmd/display? + hide=true + ;; + --title) # use custom message as title, but keep command output + title="${2}\\n" # add newline, such that command output will go to next line + unset hide # implicitly enable output + shift + ;; + --display) # use custom message instead of command output + display="${2}" + unset hide # implicitly enable output + shift + ;; + --timing) # enable timing? + timing=true + ;; + --timeout) # abore commands after a timeout + timeout="${2}" + shift + ;; + *) break ;; + esac shift + done + + cmds="$*" + export TRAVIS_CMD="${cmds}" + + if [ -n "${timing}" ]; then + travis_time_start + fi + + if [ -z "${hide}" ]; then + echo -e "${ANSI_BLUE}${ANSI_THIN}${title}${display:-${cmds}}${ANSI_RESET}" + fi + + # Actually run cmds + if [ -n "${timeout}" ]; then + (eval "${cmds}") & # run cmds in subshell in background + travis_wait $! $timeout "$cmds" # wait for the subshell process to finish + result="${?}" + test $result -eq 124 && \ + echo -e "\\n${ANSI_YELLOW}Timeout (${timeout} minutes) reached.${ANSI_RESET}\\n" else - # default value - timeout=20 + eval "${cmds}" + result="${?}" + fi + + if [ -n "${timing}" ]; then + travis_time_finish fi - local commands=$@ - let "TRAVIS_FOLD_COUNTER += 1" - travis_time_start moveit_ci.$TRAVIS_FOLD_COUNTER $commands + # When asserting success, but we got a failure (and not a timeout (124)), terminate + if [ -n "${assert}" -a $result -ne 0 -a $result -ne 124 ]; then + echo -e "${ANSI_RED}The command \"${TRAVIS_CMD}\" failed and exited with ${result}.${ANSI_RESET}\\n" + exit 2 +# travis_terminate 2 + fi + + return "${result}" +} - # Disable bash's job control messages - set +m - # actually run commands, eval needed to handle multiple commands! - eval $commands & - local cmd_pid=$! +# Run command(s) with timing, but without folding +travis_run_simple() { + travis_run_impl --timing --assert "$@" +} + +# Run command(s) with folding and timing, ignoring failure +travis_run_true() { + travis_fold start + travis_run_impl --timing "$@" + local result=$? + travis_fold end + return $result +} + +# Run command(s) with timing, folding, and terminate on failure +travis_run() { + # add option --assert to travis_run_true + travis_run_true --assert "$@" +} + +# Run command(s) with a timeout (of 20min by default) +travis_run_wait() { + # parse first parameter as timeout and drop it if successful + local timeout + timeout=$(travis_timeout "$1") && shift + travis_run_true --assert --timeout "${timeout}" "$@" +} - # Start jigger process, taking care of the timeout and '.' outputs - travis_jigger $cmd_pid $timeout $commands & - local jigger_pid=$! +# Wait for the passed process to finish +# Adapted from travis_wait.bash +travis_wait() { + local cmd_pid=$1 # we are waiting for this process to finish + local timeout=$2 # timeout in mins + + travis_monitor $cmd_pid $timeout & # start monitoring process in background + local monitor_pid=$! # Wait for main command to finish wait $cmd_pid 2>/dev/null - local result=$? - # If main process finished before jigger, stop the jigger too - # https://stackoverflow.com/questions/81520/how-to-suppress-terminated-message-after-killing-in-bash - kill $jigger_pid 2> /dev/null && wait $! 2> /dev/null + local result=$? # result of the main process - echo - travis_time_end + if ! ps -p $monitor_pid &>/dev/null ; then + # If monitor process is not running anymore, we timed out + result=124 + else + # kill monitor process + kill $monitor_pid 2> /dev/null && wait $! 2> /dev/null + # https://stackoverflow.com/questions/81520/how-to-suppress-terminated-message-after-killing-in-bash + fi - test $result -eq 0 || exit $result + return $result # return result of main process } -####################################### -function travis_jigger() { - local cmd_pid=$1 - shift - local timeout=$1 - shift - local count=0 +# adapted from travis_jigger.bash +travis_monitor() { + local cmd_pid=$1 # we are waiting for this process to finish + local timeout=$2 # timeout in mins + local elapsed=0 # elapsed time in mins - while [ $count -lt $timeout ]; do - count=$(($count + 1)) + while [ "${elapsed}" -lt "${timeout}" ]; do + elapsed="$((elapsed + 1))" sleep 60 # wait 60s echo -ne "." done - echo -e "\n\033[31;1mTimeout (${timeout} minutes) reached. Terminating \"$@\"\033[0m\n" - kill -9 $cmd_pid + kill -9 $cmd_pid # kill monitored process +} + +# Check repository for changes, return success(0) if there are changes +travis_have_fixes() { + if ! git diff-index --quiet HEAD --; then + echo -e "${ANSI_RED}\\nThe following issues were detected:${ANSI_RESET}" + git --no-pager diff + git reset --hard HEAD # undo changes + return 0 + fi + return 1 +} + +# $(filter "PATTERN" "TEXT") +# Returns all words in TEXT that *do* match any of the PATTERN words, +# removing any words that *do not* match. +# words can be separated by space, comma, semicolon or newline +filter() { + local PATTERN="$1"; shift + # convert input lists into newline-separate lists: | tr ' ;,' '\n' + # perform filtering: grep -Fvxf + # and convert newlines back to spaces: | tr '\n' ' ' + echo "$*" | tr ' ;,' '\n' | grep -Fxf <(echo "$PATTERN" | tr ' ;,' '\n') | tr '\n' ' ' +} + +# $(filter-out "PATTERN" "TEXT") +# Returns all words in TEXT that *do not* match any of the PATTERN words, +# removing the words that *do match* one or more. +# This is the exact opposite of the filter function. +# words can be separated by space, comma, semicolon or newline +filter-out() { + local PATTERN="$1"; shift + # convert input lists into newline-separate lists: | tr ' ;,' '\n' + # perform filtering: grep -Fvxf + # and convert newlines back to spaces: | tr '\n' ' ' + echo "$*" | tr ' ;,' '\n' | grep -Fvxf <(echo "$PATTERN" | tr ' ;,' '\n') | tr '\n' ' ' }