#!/bin/bash
# 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.

# Checks that the "_svn" function defined in the specified "bash_completion"
# script produces appropriate lists of completions for various incomplete svn
# command lines.

THIS_DIR=`dirname "$0"`
SCRIPT="$1"
if [ -z "$SCRIPT" ]; then
  SCRIPT="$THIS_DIR/bash_completion"
fi

if [ ! -r "$SCRIPT" ] || [ "$2" ]; then
  echo "Usage: bash_completion_test [BASH_COMPLETION_PATHNAME]"
  echo "Tests the specified \"bash_completion\" script,"
  echo "defaulting to the one in the same directory as this test,"
  echo "including checking it against the \"svn\" program found in the current PATH."
  exit 1
fi

set -e  # Exit on error
shopt -s extglob
export LC_ALL=C

# Execute the script which is to be tested.
. "$SCRIPT"

# From the given incomplete command, print a space-separated list of
# possible completions of the last argument (or of an empty first argument
# if no subcommand is given).
#
# Usage: get_completions SVN-CMD [SVN-SUBCOMMAND [SVN-OPTION...]]
# where SVN-CMD is "svn", "svnadmin", etc.; such that when a leading
# underscore is added, it must name one of the completion functions in
# "bash_completion".
get_completions() {
  SVN_CMD="$1"
  COMP_WORDS=("$@")
  if [ $# == 1 ]; then
    COMP_CWORD=1
  else
    COMP_CWORD=$(($#-1))
  fi
  # Call the appropriate completion function (e.g. "_svn") with no arguments.
  "_$SVN_CMD"
  echo -n "${COMPREPLY[*]}"
}

# Print a failure message, record the failure, and return "false".
# Usage: fail MESSAGE
fail() {
  PREFIX="FAIL: "
  for LINE in "$@"; do
    echo "$PREFIX$LINE"
    PREFIX="      "
  done
  TESTS_FAILED=1
  false
}

# Check that EXPECTED-WORD is among the completions of the last word in
# SVN-ARGS.  SVN-ARGS is a single argument to this function, split
# into multiple arguments when passed to "get_completions()".
# Usage: includes SVN-CMD SVN-ARGS EXPECTED-WORD
includes() {
  SVN_CMD="$1"
  SVN_ARGS="$2"
  EXPECTED_WORD="$3"
  COMPLETIONS=`get_completions "$SVN_CMD" $SVN_ARGS`
  if [[ "$EXPECTED_WORD" != @(${COMPLETIONS// /|}) ]]; then
    fail "completions of \"$SVN_CMD $SVN_ARGS\" should include \"$EXPECTED_WORD\"" \
      "(completions: $COMPLETIONS)"
  fi
}

excludes() {
  SVN_CMD="$1"
  SVN_ARGS="$2"
  EXPECTED_WORD="$3"
  COMPLETIONS=`get_completions "$SVN_CMD" $SVN_ARGS`
  if [[ "$EXPECTED_WORD" == @(${COMPLETIONS// /|}) ]]; then
    fail "completions of \"$SVN_CMD $SVN_ARGS\" should exclude \"$EXPECTED_WORD\"" \
      "(completions: $COMPLETIONS)"
  fi
}

# Print the valid subcommands for an "svn"-like program, one per line, sorted.
# Exclude any synonym that is just a truncation of its full name.
# Usage: get_svn_subcommands SVN-CMD
# where SVN-CMD is "svn" or another program that outputs similar help.
get_svn_subcommands() {
  SVN_CMD="$1"
  "$SVN_CMD" help |
    # Find the relevant lines.
    sed -n -e '1,/^Available subcommands:$/d;/^$/q;p' |
    # Remove brackets and commas
    tr -d ' )' | tr '(,' ' ' |
    # Remove simple abbreviations
    ( while read SYNONYMS; do
        for CMD in $SYNONYMS; do
          if [ "$CMD" != "?" ]; then
            for SYNONYM in $SYNONYMS; do
              case $SYNONYM in
              $CMD) ;;
              $CMD*) CMD= ; break ;;
              esac
            done
            if [ $CMD ]; then
              echo $CMD
            fi
          fi
        done
      done
    ) |
    sort
}

# Print the valid option switches for "svn SUBCMD", one per line, sorted.
# Usage: get_svn_options SVN-CMD SUBCMD
# where SVN-CMD is "svn" or another program that outputs similar help.
get_svn_options() {
  SVN_CMD="$1"
  SUBCMD="$2"
  { "$SVN_CMD" help "$SUBCMD" |
      # Remove deprecated options
      grep -v deprecated |
      # Find the relevant lines; remove "arg" and description.
      sed -n -e '1,/^\(Valid\|Global\) options:$/d;/^  -/!d' \
             -e 's/\( ARG\)* * : .*//;p' |
      # Remove brackets; put each word on its own line.
      tr -d '] ' | tr '[' '\n'
    # The following options are always accepted but not listed in the help
    if [ "$SUBCMD" != "help" ] ; then
      echo "-h"
      echo "--help"
    fi
  } | sort
  
}


# The tests.
set +e  # Do not exit on error
TESTS_FAILED=

echo "Checking general completion"
includes svn "he" "help"
includes svn "" "help"
includes svn "" "--version"

for SVN_CMD in svn svnadmin svndumpfilter svnlook svnrdump svnsync; do
  echo "Checking list of subcommands: $SVN_CMD"
  HELP_SUBCMDS=`get_svn_subcommands "$SVN_CMD" | tr "\n" " "`
  COMPLETION_SUBCMDS=`get_completions "$SVN_CMD" | tr " " "\n" | grep -v "^-" | sort | tr "\n" " "`
  if [ "$HELP_SUBCMDS" != "$COMPLETION_SUBCMDS" ]; then
    fail "non-option completions for \"$SVN_CMD\" != subcommands accepted" \
         "    (non-o. cmpl.: $COMPLETION_SUBCMDS)" \
         "    (help says:    $HELP_SUBCMDS)"
  fi

  echo "Checking list of options for each subcommand"
  for SUBCMD in $HELP_SUBCMDS; do
    HELP_OPTIONS=`get_svn_options $SVN_CMD $SUBCMD | tr "\n" " "`
    COMPLETION_OPTIONS=`get_completions $SVN_CMD $SUBCMD - | tr " " "\n" | sort | tr "\n" " "`
    if [ "$HELP_OPTIONS" != "$COMPLETION_OPTIONS" ]; then
      fail "completions for \"$SVN_CMD $SUBCMD -\" != options accepted" \
           "    (completions: $COMPLETION_OPTIONS)" \
           "    (help says:   $HELP_OPTIONS)"
    fi
  done
done

echo "Checking rejection of synonyms"
excludes svn "diff -x -u -" "-x"
excludes svn "diff -x -u --e" "--extensions"
excludes svn "diff --extensions -u -" "--extensions"
excludes svn "diff --extensions -u -" "-x"
excludes svn "diff --extensions=-u -" "-x"

if [ $TESTS_FAILED ]; then
  echo "FAILURE: at least one bash_completion test failed."
else
  echo "All bash_completion tests passed."
fi
