From dc9c78b210219d8dc213092b21cb0fdc6d8cd554 Mon Sep 17 00:00:00 2001 From: Ian Howell Date: Wed, 2 Oct 2019 11:05:59 -0500 Subject: [PATCH] Fix the coverage tests This fixes a bug where `make cover` was missing packages. The target now covers all code except for code placed in the top level package (such as main.go) and anything placed in the testutils directory. This also fixes minor issues with the Dockerfile and the coverage_check script Note that this commit also strives to increase code coverage beyond the 80% margin Change-Id: I9e1cbcf841cc869345a00f05e39774cb3da10065 --- Dockerfile | 6 +- Makefile | 18 +- cmd/completion/completion_test.go | 48 ++ .../completion-bash.golden | 303 ++++++++++++ .../completion-no-args.golden | 7 + .../completion-too-many-args.golden | 7 + .../completion-unknown-shell.golden | 7 + .../completion-zsh.golden | 441 ++++++++++++++++++ pkg/util/configreader_test.go | 22 + pkg/util/testdata/test.yaml | 1 + testutil/utilities.go | 39 +- tools/coverage_check | 2 +- 12 files changed, 886 insertions(+), 15 deletions(-) create mode 100644 cmd/completion/completion_test.go create mode 100644 cmd/completion/testdata/TestCompletionGoldenOutput/completion-bash.golden create mode 100644 cmd/completion/testdata/TestCompletionGoldenOutput/completion-no-args.golden create mode 100644 cmd/completion/testdata/TestCompletionGoldenOutput/completion-too-many-args.golden create mode 100644 cmd/completion/testdata/TestCompletionGoldenOutput/completion-unknown-shell.golden create mode 100644 cmd/completion/testdata/TestCompletionGoldenOutput/completion-zsh.golden create mode 100644 pkg/util/configreader_test.go create mode 100644 pkg/util/testdata/test.yaml diff --git a/Dockerfile b/Dockerfile index 77ec8e8da..2a4a11984 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,11 +10,7 @@ ENV GO111MODULE=on RUN make get-modules ARG MAKE_TARGET=build -RUN make ${MAKE_TARGET} && \ - if [[ "${MAKE_TARGET}" == 'lint' ]]; then \ - mkdir -p /usr/src/airshipctl/bin; \ - touch /usr/src/airshipctl/bin/airshipctl; \ - fi +RUN make ${MAKE_TARGET} FROM ${RELEASE_IMAGE} as release COPY --from=builder /usr/src/airshipctl/bin/airshipctl /usr/local/bin/airshipctl diff --git a/Makefile b/Makefile index 7871ce18b..2c3c3e4b0 100644 --- a/Makefile +++ b/Makefile @@ -18,11 +18,15 @@ DOCKER_IMAGE_NAME ?= airshipctl DOCKER_IMAGE_PREFIX ?= airshipit DOCKER_IMAGE_TAG ?= dev DOCKER_IMAGE ?= $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_PREFIX)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) +DOCKER_TARGET_STAGE ?= release # go options PKG := ./... TESTS := . +TEST_FLAGS := +COVER_FLAGS := COVER_PROFILE := cover.out +COVER_PKG := $(shell go list ./... | tail -n+2 | grep -v "opendev.org/airship/airshipctl/testutil" | paste -sd"," -) .PHONY: get-modules get-modules: @@ -34,17 +38,17 @@ build: get-modules .PHONY: test test: lint -test: TESTFLAGS += -race -v -test: unit-tests test: cover .PHONY: unit-tests -unit-tests: build +unit-tests: TESTFLAGS += -race -v +unit-tests: @echo "Performing unit test step..." - @GO111MODULE=on go test -run $(TESTS) $(PKG) $(TESTFLAGS) -covermode=atomic -coverprofile=$(COVER_PROFILE) + @GO111MODULE=on go test -run $(TESTS) $(PKG) $(TESTFLAGS) $(COVER_FLAGS) @echo "All unit tests passed" .PHONY: cover +cover: COVER_FLAGS = -covermode=atomic -coverprofile=$(COVER_PROFILE) -coverpkg=$(COVER_PKG) cover: unit-tests @./tools/coverage_check $(COVER_PROFILE) @@ -56,7 +60,7 @@ lint: .PHONY: docker-image docker-image: - @docker build . --build-arg MAKE_TARGET=$(DOCKER_MAKE_TARGET) --tag $(DOCKER_IMAGE) + @docker build . --build-arg MAKE_TARGET=$(DOCKER_MAKE_TARGET) --tag $(DOCKER_IMAGE) --target $(DOCKER_TARGET_STAGE) .PHONY: print-docker-image-tag print-docker-image-tag: @@ -64,10 +68,12 @@ print-docker-image-tag: .PHONY: docker-image-unit-tests docker-image-unit-tests: DOCKER_MAKE_TARGET = cover +docker-image-unit-tests: DOCKER_TARGET_STAGE = builder docker-image-unit-tests: docker-image .PHONY: docker-image-lint docker-image-lint: DOCKER_MAKE_TARGET = lint +docker-image-lint: DOCKER_TARGET_STAGE = builder docker-image-lint: docker-image .PHONY: clean @@ -81,7 +87,7 @@ docs: .PHONY: update-golden update-golden: delete-golden -update-golden: TESTFLAGS += -update -v +update-golden: TESTFLAGS += -update update-golden: PKG = opendev.org/airship/airshipctl/cmd/... update-golden: unit-tests diff --git a/cmd/completion/completion_test.go b/cmd/completion/completion_test.go new file mode 100644 index 000000000..7b56d05c5 --- /dev/null +++ b/cmd/completion/completion_test.go @@ -0,0 +1,48 @@ +package completion_test + +import ( + "errors" + "testing" + + "opendev.org/airship/airshipctl/cmd/completion" + "opendev.org/airship/airshipctl/testutil" +) + +func TestCompletion(t *testing.T) { + cmd := completion.NewCompletionCommand() + + cmdTests := []*testutil.CmdTest{ + { + Name: "completion-bash", + CmdLine: "bash", + Cmd: cmd, + }, + { + Name: "completion-zsh", + CmdLine: "zsh", + Cmd: cmd, + }, + { + Name: "completion-no-args", + CmdLine: "", + Cmd: cmd, + Error: errors.New("shell not specified"), + }, + { + Name: "completion-too-many-args", + CmdLine: "bash zsh", + Cmd: cmd, + Error: errors.New("too many arguments, expected only the shell type"), + }, + { + Name: "completion-unknown-shell", + CmdLine: "fish", + Cmd: cmd, + Error: errors.New("unsupported shell type \"fish\""), + }, + } + + for _, tt := range cmdTests { + testutil.RunTest(t, tt) + } +} diff --git a/cmd/completion/testdata/TestCompletionGoldenOutput/completion-bash.golden b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-bash.golden new file mode 100644 index 000000000..553654e82 --- /dev/null +++ b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-bash.golden @@ -0,0 +1,303 @@ +# bash completion for completion -*- shell-script -*- + +__completion_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__completion_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__completion_index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__completion_contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__completion_handle_reply() +{ + __completion_debug "${FUNCNAME[0]}" + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%=*}" + __completion_index_of_word "${flag}" "${flags_with_completion[@]}" + COMPREPLY=() + if [[ ${index} -ge 0 ]]; then + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION}" ]; then + # zsh completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + return 0; + ;; + esac + + # check if we are handling a flag with special work handling + local index + __completion_index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions=("${must_have_one_noun[@]}") + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + declare -F __custom_func >/dev/null && __custom_func + fi + + # available in bash-completion >= 2, not always present on macOS + if declare -F __ltrim_colon_completions >/dev/null; then + __ltrim_colon_completions "$cur" + fi + + # If there is only 1 completion and it is a flag with an = it will be completed + # but we don't want a space after the = + if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then + compopt -o nospace + fi +} + +# The arguments should be in the form "ext1|ext2|extn" +__completion_handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__completion_handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 +} + +__completion_handle_flag() +{ + __completion_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __completion_debug "${FUNCNAME[0]}: looking for ${flagname}" + if __completion_contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __completion_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + # flaghash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + fi + + # skip the argument to a two word flag + if __completion_contains_word "${words[c]}" "${two_word_flags[@]}"; then + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__completion_handle_noun() +{ + __completion_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __completion_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __completion_contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__completion_handle_command() +{ + __completion_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_completion_root_command" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __completion_debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F "$next_command" >/dev/null && $next_command +} + +__completion_handle_word() +{ + if [[ $c -ge $cword ]]; then + __completion_handle_reply + return + fi + __completion_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __completion_handle_flag + elif __completion_contains_word "${words[c]}" "${commands[@]}"; then + __completion_handle_command + elif [[ $c -eq 0 ]]; then + __completion_handle_command + elif __completion_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __completion_handle_command + else + __completion_handle_noun + fi + else + __completion_handle_noun + fi + __completion_handle_word +} + +_completion_root_command() +{ + last_command="completion" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--help") + flags+=("-h") + local_nonpersistent_flags+=("--help") + + must_have_one_flag=() + must_have_one_noun=() + must_have_one_noun+=("bash") + must_have_one_noun+=("zsh") + noun_aliases=() +} + +__start_completion() +{ + local cur prev words cword + declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __completion_init_completion -n "=" || return + fi + + local c=0 + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("completion") + local must_have_one_flag=() + local must_have_one_noun=() + local last_command + local nouns=() + + __completion_handle_word +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_completion completion +else + complete -o default -o nospace -F __start_completion completion +fi + +# ex: ts=4 sw=4 et filetype=sh diff --git a/cmd/completion/testdata/TestCompletionGoldenOutput/completion-no-args.golden b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-no-args.golden new file mode 100644 index 000000000..5e588c0ca --- /dev/null +++ b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-no-args.golden @@ -0,0 +1,7 @@ +Error: shell not specified +Usage: + completion SHELL [flags] + +Flags: + -h, --help help for completion + diff --git a/cmd/completion/testdata/TestCompletionGoldenOutput/completion-too-many-args.golden b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-too-many-args.golden new file mode 100644 index 000000000..22323a2f4 --- /dev/null +++ b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-too-many-args.golden @@ -0,0 +1,7 @@ +Error: too many arguments, expected only the shell type +Usage: + completion SHELL [flags] + +Flags: + -h, --help help for completion + diff --git a/cmd/completion/testdata/TestCompletionGoldenOutput/completion-unknown-shell.golden b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-unknown-shell.golden new file mode 100644 index 000000000..f6d6a1635 --- /dev/null +++ b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-unknown-shell.golden @@ -0,0 +1,7 @@ +Error: unsupported shell type "fish" +Usage: + completion SHELL [flags] + +Flags: + -h, --help help for completion + diff --git a/cmd/completion/testdata/TestCompletionGoldenOutput/completion-zsh.golden b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-zsh.golden new file mode 100644 index 000000000..77193d2cd --- /dev/null +++ b/cmd/completion/testdata/TestCompletionGoldenOutput/completion-zsh.golden @@ -0,0 +1,441 @@ +#compdef airshipctl + +__airshipctl_bash_source() { + alias shopt=':' + alias _expand=_bash_expand + alias _complete=_bash_comp + emulate -L sh + setopt kshglob noshglob braceexpand + source "$@" +} +__airshipctl_type() { + # -t is not supported by zsh + if [ "$1" == "-t" ]; then + shift + # fake Bash 4 to disable "complete -o nospace". Instead + # "compopt +-o nospace" is used in the code to toggle trailing + # spaces. We don't support that, but leave trailing spaces on + # all the time + if [ "$1" = "__airshipctl_compopt" ]; then + echo builtin + return 0 + fi + fi + type "$@" +} +__airshipctl_compgen() { + local completions w + completions=( $(compgen "$@") ) || return $? + # filter by given word as prefix + while [[ "$1" = -* && "$1" != -- ]]; do + shift + shift + done + if [[ "$1" == -- ]]; then + shift + fi + for w in "${completions[@]}"; do + if [[ "${w}" = "$1"* ]]; then + echo "${w}" + fi + done +} +__airshipctl_compopt() { + true # don't do anything. Not supported by bashcompinit in zsh +} +__airshipctl_declare() { + if [ "$1" == "-F" ]; then + whence -w "$@" + else + builtin declare "$@" + fi +} +__airshipctl_ltrim_colon_completions() +{ + if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then + # Remove colon-word prefix from COMPREPLY items + local colon_word=${1%${1##*:}} + local i=${#COMPREPLY[*]} + while [[ $((--i)) -ge 0 ]]; do + COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} + done + fi +} +__airshipctl_get_comp_words_by_ref() { + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[${COMP_CWORD}-1]}" + words=("${COMP_WORDS[@]}") + cword=("${COMP_CWORD[@]}") +} +__airshipctl_filedir() { + local RET OLD_IFS w qw + __debug "_filedir $@ cur=$cur" + if [[ "$1" = \~* ]]; then + # somehow does not work. Maybe, zsh does not call this at all + eval echo "$1" + return 0 + fi + OLD_IFS="$IFS" + IFS=$'\n' + if [ "$1" = "-d" ]; then + shift + RET=( $(compgen -d) ) + else + RET=( $(compgen -f) ) + fi + IFS="$OLD_IFS" + IFS="," __debug "RET=${RET[@]} len=${#RET[@]}" + for w in ${RET[@]}; do + if [[ ! "${w}" = "${cur}"* ]]; then + continue + fi + if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then + qw="$(__airshipctl_quote "${w}")" + if [ -d "${w}" ]; then + COMPREPLY+=("${qw}/") + else + COMPREPLY+=("${qw}") + fi + fi + done +} +__airshipctl_quote() { + if [[ $1 == \'* || $1 == \"* ]]; then + # Leave out first character + printf %q "${1:1}" + else + printf %q "$1" + fi +} +autoload -U +X bashcompinit && bashcompinit +# use word boundary patterns for BSD or GNU sed +LWORD='[[:<:]]' +RWORD='[[:>:]]' +if sed --help 2>&1 | grep -q GNU; then + LWORD='\<' + RWORD='\>' +fi +__airshipctl_convert_bash_to_zsh() { + sed \ + -e 's/declare -F/whence -w/' \ + -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \ + -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ + -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ + -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ + -e "s/${LWORD}_filedir${RWORD}/__airshipctl_filedir/g" \ + -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__airshipctl_get_comp_words_by_ref/g" \ + -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__airshipctl_ltrim_colon_completions/g" \ + -e "s/${LWORD}compgen${RWORD}/__airshipctl_compgen/g" \ + -e "s/${LWORD}compopt${RWORD}/__airshipctl_compopt/g" \ + -e "s/${LWORD}declare${RWORD}/__airshipctl_declare/g" \ + -e "s/\\\$(type${RWORD}/\$(__airshipctl_type/g" \ + -e 's/aliashash\["\(.\{1,\}\)"\]/aliashash[\1]/g' \ + -e 's/FUNCNAME/funcstack/g' \ + <<'BASH_COMPLETION_EOF' +# bash completion for completion -*- shell-script -*- + +__completion_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__completion_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__completion_index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__completion_contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__completion_handle_reply() +{ + __completion_debug "${FUNCNAME[0]}" + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%=*}" + __completion_index_of_word "${flag}" "${flags_with_completion[@]}" + COMPREPLY=() + if [[ ${index} -ge 0 ]]; then + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION}" ]; then + # zsh completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + return 0; + ;; + esac + + # check if we are handling a flag with special work handling + local index + __completion_index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions=("${must_have_one_noun[@]}") + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + declare -F __custom_func >/dev/null && __custom_func + fi + + # available in bash-completion >= 2, not always present on macOS + if declare -F __ltrim_colon_completions >/dev/null; then + __ltrim_colon_completions "$cur" + fi + + # If there is only 1 completion and it is a flag with an = it will be completed + # but we don't want a space after the = + if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then + compopt -o nospace + fi +} + +# The arguments should be in the form "ext1|ext2|extn" +__completion_handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__completion_handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 +} + +__completion_handle_flag() +{ + __completion_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __completion_debug "${FUNCNAME[0]}: looking for ${flagname}" + if __completion_contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __completion_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + # flaghash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + fi + + # skip the argument to a two word flag + if __completion_contains_word "${words[c]}" "${two_word_flags[@]}"; then + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__completion_handle_noun() +{ + __completion_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __completion_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __completion_contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__completion_handle_command() +{ + __completion_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_completion_root_command" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __completion_debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F "$next_command" >/dev/null && $next_command +} + +__completion_handle_word() +{ + if [[ $c -ge $cword ]]; then + __completion_handle_reply + return + fi + __completion_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __completion_handle_flag + elif __completion_contains_word "${words[c]}" "${commands[@]}"; then + __completion_handle_command + elif [[ $c -eq 0 ]]; then + __completion_handle_command + elif __completion_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __completion_handle_command + else + __completion_handle_noun + fi + else + __completion_handle_noun + fi + __completion_handle_word +} + +_completion_root_command() +{ + last_command="completion" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--help") + flags+=("-h") + local_nonpersistent_flags+=("--help") + + must_have_one_flag=() + must_have_one_noun=() + must_have_one_noun+=("bash") + must_have_one_noun+=("zsh") + noun_aliases=() +} + +__start_completion() +{ + local cur prev words cword + declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __completion_init_completion -n "=" || return + fi + + local c=0 + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("completion") + local must_have_one_flag=() + local must_have_one_noun=() + local last_command + local nouns=() + + __completion_handle_word +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_completion completion +else + complete -o default -o nospace -F __start_completion completion +fi + +# ex: ts=4 sw=4 et filetype=sh + +BASH_COMPLETION_EOF +} +__airshipctl_bash_source <(__airshipctl_convert_bash_to_zsh) diff --git a/pkg/util/configreader_test.go b/pkg/util/configreader_test.go new file mode 100644 index 000000000..52cad248a --- /dev/null +++ b/pkg/util/configreader_test.go @@ -0,0 +1,22 @@ +package util_test + +import ( + "testing" + + "opendev.org/airship/airshipctl/pkg/util" +) + +func TestReadYAMLFile(t *testing.T) { + var actual map[string]interface{} + if err := util.ReadYAMLFile("testdata/test.yaml", &actual); err != nil { + t.Fatalf("Error while reading YAML: %s", err.Error()) + } + expectedString := "test" + actualString, ok := actual["testString"] + if !ok { + t.Fatalf("Missing \"testString\" attribute") + } + if actualString != expectedString { + t.Errorf("Expected %s, got %s", expectedString, actualString) + } +} diff --git a/pkg/util/testdata/test.yaml b/pkg/util/testdata/test.yaml new file mode 100644 index 000000000..cb8585cdc --- /dev/null +++ b/pkg/util/testdata/test.yaml @@ -0,0 +1 @@ +testString: test diff --git a/testutil/utilities.go b/testutil/utilities.go index 87773e0d7..c55a9faea 100644 --- a/testutil/utilities.go +++ b/testutil/utilities.go @@ -33,6 +33,9 @@ type CmdTest struct { // The instatiated version of the root airshipctl command to test Cmd *cobra.Command + + // The expected error + Error error } // RunTest either asserts that a specific command's output matches the expected @@ -47,9 +50,8 @@ func RunTest(t *testing.T, test *CmdTest) { args := strings.Fields(test.CmdLine) cmd.SetArgs(args) - if err := cmd.Execute(); err != nil { - t.Fatalf("Unexpected error: %s", err.Error()) - } + err := cmd.Execute() + checkError(t, err, test.Error) if *shouldUpdateGolden { updateGolden(t, test, actual.Bytes()) @@ -58,6 +60,20 @@ func RunTest(t *testing.T, test *CmdTest) { } } +// ReadFixtureBytes is a convenience function for opening a test fixture +func ReadFixtureBytes(t *testing.T, filename string) []byte { + fixtureData, err := ioutil.ReadFile(filename) + if err != nil { + t.Fatalf("Unexpected error while reading fixture at %s: %s", filename, err.Error()) + } + return fixtureData +} + +// ReadFixtureString is a convenience function for opening a test fixture +func ReadFixtureString(t *testing.T, filename string) string { + return string(ReadFixtureBytes(t, filename)) +} + func updateGolden(t *testing.T, test *CmdTest, actual []byte) { goldenDir := filepath.Join(testdataDir, t.Name()+goldenDirSuffix) if err := os.MkdirAll(goldenDir, 0775); err != nil { @@ -84,6 +100,23 @@ func assertEqualGolden(t *testing.T, test *CmdTest, actual []byte) { } } +func checkError(t *testing.T, actual, expected error) { + if expected == nil { + if actual == nil { + return + } + t.Fatalf("Unexpected error: %q", actual.Error()) + } + + if actual == nil { + t.Fatalf("Expected error %q, but got nil", expected.Error()) + } + + if actual.Error() != expected.Error() { + t.Fatalf("Expected error %q, but got %q", expected.Error(), actual.Error()) + } +} + func normalize(in []byte) []byte { return bytes.Replace(in, []byte("\r\n"), []byte("\n"), -1) } diff --git a/tools/coverage_check b/tools/coverage_check index d6634a317..203fa8c20 100755 --- a/tools/coverage_check +++ b/tools/coverage_check @@ -1,5 +1,5 @@ #!/bin/bash -set -ex +set -e if [[ $# -ne 1 ]]; then printf "Usage: %s \n" "$0"