From 1eb757f8897847c6316216e92cad8d528a8e442c Mon Sep 17 00:00:00 2001 From: Mike Bland Date: Fri, 20 Jan 2017 14:21:45 -0500 Subject: [PATCH 1/2] path, modules: Add _GO_INJECT_{SCRIPT,MODULE}_PATH First part of #118. Allows for better stubbing by providing for a controlled way to inject fake command and module scripts, even core framework modules, without having to move existing files. --- go-core.bash | 15 ++++++++++++--- lib/internal/path | 6 +++++- lib/internal/use | 11 ++++++++++- tests/core/inject-paths.bats | 27 +++++++++++++++++++++++++++ tests/vars.bats | 35 +++++++++++++++++++++++------------ 5 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 tests/core/inject-paths.bats diff --git a/go-core.bash b/go-core.bash index c1231dd..c5b34f4 100755 --- a/go-core.bash +++ b/go-core.bash @@ -120,9 +120,18 @@ declare _GO_PLUGINS_DIR= # Directories containing executable plugin scripts. declare _GO_PLUGINS_PATHS=() -# Directories that are searched for executable command scripts. After they are -# initialized, _GO_PLUGINS_PATHS and _GO_SCRIPTS_DIR will be added. -declare _GO_SEARCH_PATHS=("$_GO_CORE_DIR/libexec") +# Directories that are searched for executable command scripts. +declare _GO_SEARCH_PATHS=() + +# Directory to search for command scripts prior to _GO_SEARCH_PATHS. +# Should be an absolute path. Use this for stubbing out scripts during testing +# or debugging, or for experimenting with new implementations of existing +# scripts. +declare _GO_INJECT_SEARCH_PATH="$_GO_INJECT_SEARCH_PATH" + +# Directory to search for module scripts first. +# Similar to _GO_INJECT_SEARCH_PATHS above, but for `. "$_GO_USE_MODULES"`. +declare _GO_INJECT_MODULE_PATH="$_GO_INJECT_MODULE_PATH" # Invokes printf builtin, then folds output to $COLUMNS width # diff --git a/lib/internal/path b/lib/internal/path index d678101..4aec760 100644 --- a/lib/internal/path +++ b/lib/internal/path @@ -1,6 +1,10 @@ #! /bin/bash -if [[ "${#_GO_SEARCH_PATHS[@]}" -eq 1 ]]; then +if [[ "${#_GO_SEARCH_PATHS[@]}" -eq 0 ]]; then + if [[ -n "$_GO_INJECT_SEARCH_PATH" ]]; then + _GO_SEARCH_PATHS+=("$_GO_INJECT_SEARCH_PATH") + fi + _GO_SEARCH_PATHS+=("$_GO_CORE_DIR/libexec") if [[ -d "$_GO_SCRIPTS_DIR/plugins" ]]; then _GO_PLUGINS_DIR="$_GO_SCRIPTS_DIR/plugins" _GO_PLUGINS_PATHS=("$_GO_PLUGINS_DIR" "$_GO_PLUGINS_DIR"/*/bin) diff --git a/lib/internal/use b/lib/internal/use index 60b1b5f..f54c565 100644 --- a/lib/internal/use +++ b/lib/internal/use @@ -43,6 +43,7 @@ # The precedence for discovering modules is (with examples from the "Directory # structure" example from README.md): # +# - the `_GO_INJECT_MODULE_PATH` directory # - the `lib/` directory of the framework (`scripts/go-script-bash/lib`) # - the `lib/` directory of installed plugins (`scripts/plugins/*/lib`) # - the `lib/` directory in your project scripts directory (`scripts/lib`) @@ -80,7 +81,15 @@ for __go_module_name in "$@"; do # Prevent self- and circular importing by registering name before sourcing. _GO_IMPORTED_MODULES+=("$__go_module_name") - __go_module_file="$_GO_CORE_DIR/lib/$__go_module_name" + __go_module_file='' + + if [[ -n "$_GO_INJECT_MODULE_PATH" ]]; then + __go_module_file="$_GO_INJECT_MODULE_PATH/$__go_module_name" + if [[ ! -f "$__go_module_file" ]]; then + __go_module_file='' + fi + fi + __go_module_file="${__go_module_file:-$_GO_CORE_DIR/lib/$__go_module_name}" if [[ ! -f "$__go_module_file" ]]; then # Convert / to plugins//lib/ diff --git a/tests/core/inject-paths.bats b/tests/core/inject-paths.bats new file mode 100644 index 0000000..3e669c9 --- /dev/null +++ b/tests/core/inject-paths.bats @@ -0,0 +1,27 @@ +#! /usr/bin/env bats + +load ../environment + +setup() { + test_filter +} + +teardown() { + @go.remove_test_go_rootdir +} + +@test "$SUITE: use _GO_INJECT_SEARCH_PATH to stub out builtin command" { + @go.create_test_go_script '@go "$@"' + create_bats_test_script 'test/help' 'printf "INJECTED\n"' + + _GO_INJECT_SEARCH_PATH="$BATS_TEST_ROOTDIR/test" run "$TEST_GO_SCRIPT" help + assert_success 'INJECTED' +} + +@test "$SUITE: use _GO_INJECT_MODULE_PATH to stub out builtin module" { + @go.create_test_go_script '. "$_GO_USE_MODULES" "$@"' + create_bats_test_script 'test/log' 'printf "INJECTED\n"' + + _GO_INJECT_MODULE_PATH="$BATS_TEST_ROOTDIR/test" run "$TEST_GO_SCRIPT" log + assert_success 'INJECTED' +} diff --git a/tests/vars.bats b/tests/vars.bats index a38ad65..daa4657 100644 --- a/tests/vars.bats +++ b/tests/vars.bats @@ -44,6 +44,8 @@ quotify_expected() { "declare -rx _GO_CORE_VERSION=\"$_GO_CORE_VERSION\"" "declare -x _GO_COVERALLS_URL=\"$_GO_COVERALLS_URL\"" 'declare -a _GO_IMPORTED_MODULES=()' + 'declare -- _GO_INJECT_MODULE_PATH=""' + 'declare -- _GO_INJECT_SEARCH_PATH=""' "declare -x _GO_KCOV_DIR=\"$_GO_KCOV_DIR\"" 'declare -- _GO_PLUGINS_DIR=""' 'declare -a _GO_PLUGINS_PATHS=()' @@ -60,14 +62,22 @@ quotify_expected() { @test "$SUITE: all _GO_* variables for Bash subcommand contain values" { @go.create_test_command_script 'test-command.d/test-subcommand' \ - '. "$_GO_USE_MODULES" "complete" "format"' \ + '. "$_GO_USE_MODULES" "module_0" "module_1"' \ '@go vars' + mkdir -p "$TEST_GO_ROOTDIR/lib" + printf '' >"$TEST_GO_ROOTDIR/lib/module_0" + printf '' >"$TEST_GO_ROOTDIR/lib/module_1" + mkdir "$TEST_GO_PLUGINS_DIR" mkdir "$TEST_GO_PLUGINS_DIR/plugin"{0,1,2} mkdir "$TEST_GO_PLUGINS_DIR/plugin"{0,1,2}"/bin" - run "$TEST_GO_SCRIPT" test-command test-subcommand foo bar 'baz quux' xyzzy + # Note that defining the `_GO_INJECT_*_PATH` variables before the `run` + # command causes them to be exported. + _GO_INJECT_SEARCH_PATH="$TEST_GO_ROOTDIR/bin" \ + _GO_INJECT_MODULE_PATH="$TEST_GO_ROOTDIR/lib" \ + run "$TEST_GO_SCRIPT" test-command test-subcommand foo bar 'baz quux' xyzzy assert_success local cmd_argv=('[0]="foo"' '[1]="bar"' '[2]="baz quux"' '[3]="xyzzy"') @@ -75,18 +85,17 @@ quotify_expected() { "[1]=\"$TEST_GO_PLUGINS_DIR/plugin0/bin\"" "[2]=\"$TEST_GO_PLUGINS_DIR/plugin1/bin\"" "[3]=\"$TEST_GO_PLUGINS_DIR/plugin2/bin\"") - local search_paths=("[0]=\"$_GO_CORE_DIR/libexec\"" - "[1]=\"$TEST_GO_PLUGINS_DIR\"" - "[2]=\"$TEST_GO_PLUGINS_DIR/plugin0/bin\"" - "[3]=\"$TEST_GO_PLUGINS_DIR/plugin1/bin\"" - "[4]=\"$TEST_GO_PLUGINS_DIR/plugin2/bin\"" - "[5]=\"$TEST_GO_SCRIPTS_DIR\"") + local search_paths=("[0]=\"$TEST_GO_ROOTDIR/bin\"" + "[1]=\"$_GO_CORE_DIR/libexec\"" + "[2]=\"$TEST_GO_PLUGINS_DIR\"" + "[3]=\"$TEST_GO_PLUGINS_DIR/plugin0/bin\"" + "[4]=\"$TEST_GO_PLUGINS_DIR/plugin1/bin\"" + "[5]=\"$TEST_GO_PLUGINS_DIR/plugin2/bin\"" + "[6]=\"$TEST_GO_SCRIPTS_DIR\"") # Note that the `format` module imports `strings` and `validation`. - local expected_modules=('[0]="complete"' - '[1]="format"' - '[2]="strings"' - '[3]="validation"') + local expected_modules=('[0]="module_0"' + '[1]="module_1"') local expected=("declare -x _GO_BATS_COVERAGE_DIR=\"$_GO_BATS_COVERAGE_DIR\"" "declare -x _GO_BATS_DIR=\"$_GO_BATS_DIR\"" "declare -x _GO_BATS_PATH=\"$_GO_BATS_PATH\"" @@ -101,6 +110,8 @@ quotify_expected() { "declare -rx _GO_CORE_VERSION=\"$_GO_CORE_VERSION\"" "declare -x _GO_COVERALLS_URL=\"$_GO_COVERALLS_URL\"" "declare -a _GO_IMPORTED_MODULES=(${expected_modules[*]})" + "declare -x _GO_INJECT_MODULE_PATH=\"$TEST_GO_ROOTDIR/lib\"" + "declare -x _GO_INJECT_SEARCH_PATH=\"$TEST_GO_ROOTDIR/bin\"" "declare -x _GO_KCOV_DIR=\"$_GO_KCOV_DIR\"" "declare -- _GO_PLUGINS_DIR=\"$TEST_GO_PLUGINS_DIR\"" "declare -a _GO_PLUGINS_PATHS=(${plugins_paths[*]})" From 7d150ae30f0bf3a6ea3f2dc188419d99f2a06111 Mon Sep 17 00:00:00 2001 From: Mike Bland Date: Fri, 20 Jan 2017 15:27:40 -0500 Subject: [PATCH 2/2] testing: Update stubbing to use _GO_INJECT_*_PATH Closes #118. With the `_GO_INJECT_SEARCH_PATH` and `_GO_INJECT_MODULE_PATH` mechanism in-place, stubbing can be done easily and safely without the need to move and restore core framework files. --- lib/testing/stubbing | 57 ++++++++++++++++--------------------- tests/test.bats | 3 +- tests/testing/stubbing.bats | 33 +++++++-------------- 3 files changed, 36 insertions(+), 57 deletions(-) diff --git a/lib/testing/stubbing b/lib/testing/stubbing index c4c9a6c..624af1d 100644 --- a/lib/testing/stubbing +++ b/lib/testing/stubbing @@ -1,43 +1,36 @@ #! /bin/bash # -# Helper functions for creating test stubs for core framework elements - -# Replace a module in `_GO_CORE_DIR/lib` with a stub implementation +# Helper functions for creating test stubs for command and module scripts # -# THIS IS POTENTIALLY DANGEROUS and you MUST call -# `@go.restore_stubbed_core_modules` in your `teardown` function if you use it! +# Useful when you need to model command script or module behavior, but don't +# want it to do real work or want to avoid a potentially complicated test setup. # -# Useful when you need to model core module behavior, but don't want it to do -# real work or want to avoid a potentially complicated test setup. +# This will assign and export values for `_GO_INJECT_SEARCH_PATH` and +# `_GO_INJECT_MODULE_PATH`. # -# Arguments: -# module_name: Name of the module to stub from `_GO_CORE_DIR/lib` -# ...: Lines comprising the stubbed module implementation -@go.create_core_module_stub() { - local module_path="$_GO_CORE_DIR/lib/$1" - shift +# You must import `_GO_CORE_DIR/lib/testing/environment` before importing this +# script, and make sure to call `@go.remove_test_go_rootdir` from `teardown`. - if [[ ! -f "$module_path" ]]; then - echo "No such core module: $module_path" >&2 - return 1 - fi +export _GO_INJECT_SEARCH_PATH="$TEST_GO_ROOTDIR/test-bin" +export _GO_INJECT_MODULE_PATH="$TEST_GO_ROOTDIR/test-lib" - cp "$module_path"{,.stubbed} - echo '#! /bin/bash' > "$module_path" - printf '%s\n' "$@" >>"$module_path" - chmod 600 "$module_path" +# Creates a stub command script implementation in `_GO_INJECT_SEARCH_PATH` +# +# Arguments: +# module_name: Name of the command script to stub +# ...: Lines comprising the stubbed command script implementation +@go.create_command_script_test_stub() { + local script_path="$_GO_INJECT_SEARCH_PATH/$1" + create_bats_test_script "${script_path#$BATS_TEST_ROOTDIR/}" "${@:2}" } -# Restore all core modules stubbed by `@go.create_core_module_stub` +# Creates a stub module implementation in `_GO_INJECT_MODULE_PATH` # -# YOU MUST CALL THIS FROM TEARDOWN IF YOU USE `@go.create_core_module_stub`! -@go.restore_stubbed_core_modules() { - local module - local stubbed_modules=("$_GO_CORE_DIR/lib"/*.stubbed) - - if [[ "${stubbed_modules#$_GO_CORE_DIR/lib/}" != '*.stubbed' ]]; then - for module in "${stubbed_modules[@]}"; do - mv "$module" "${module%.stubbed}" - done - fi +# Arguments: +# module_name: Name of the module to stub +# ...: Lines comprising the stubbed module implementation +@go.create_module_test_stub() { + local module_path="$_GO_INJECT_MODULE_PATH/$1" + create_bats_test_script "${module_path#$BATS_TEST_ROOTDIR/}" "${@:2}" + chmod 600 "$module_path" } diff --git a/tests/test.bats b/tests/test.bats index c763390..6338b5c 100644 --- a/tests/test.bats +++ b/tests/test.bats @@ -6,7 +6,6 @@ load environment load "$_GO_CORE_DIR/lib/testing/stubbing" teardown() { - @go.restore_stubbed_core_modules @go.remove_test_go_rootdir } @@ -104,7 +103,7 @@ write_bats_dummy_stub_kcov_lib_and_copy_test_script() { create_bats_test_script "tests/bats/libexec/bats" # Stub the kcov lib to assert it's called correctly. - @go.create_core_module_stub 'kcov-ubuntu' \ + @go.create_module_test_stub 'kcov-ubuntu' \ "run_kcov() { IFS=\$'\n'; echo \"\$*\"; }" if [[ ! -d "$TEST_GO_SCRIPTS_DIR" ]]; then diff --git a/tests/testing/stubbing.bats b/tests/testing/stubbing.bats index d6217a1..1568efe 100644 --- a/tests/testing/stubbing.bats +++ b/tests/testing/stubbing.bats @@ -11,29 +11,16 @@ teardown() { @go.remove_test_go_rootdir } -@test "$SUITE: create_core_module_stub and restore_stubbed_core_modules" { - [ -e "$_GO_CORE_DIR/lib/log" ] - [ ! -e "$_GO_CORE_DIR/lib/log.stubbed" ] - @go.create_core_module_stub 'log' 'echo Hello, World!' - [ -e "$_GO_CORE_DIR/lib/log.stubbed" ] - [ -e "$_GO_CORE_DIR/lib/log" ] - - @go.create_test_go_script '. "$_GO_USE_MODULES" log' - run "$TEST_GO_SCRIPT" - - @go.restore_stubbed_core_modules - [ ! -e "$_GO_CORE_DIR/lib/log.stubbed" ] - [ -e "$_GO_CORE_DIR/lib/log" ] - assert_success 'Hello, World!' -} - -@test "$SUITE: restore_stubbed_core_modules does nothing if no stubs exist" { - run @go.restore_stubbed_core_modules - assert_success '' +@test "$SUITE: create_command_script_test_stub to stub out builtin command" { + @go.create_test_go_script '@go "$@"' + @go.create_command_script_test_stub 'help' 'printf "INJECTED\n"' + run "$TEST_GO_SCRIPT" help + assert_success 'INJECTED' } -@test "$SUITE: create_core_module_stub aborts if module unknown" { - [ ! -e "$_GO_CORE_DIR/lib/foobar" ] - run @go.create_core_module_stub 'foobar' 'echo Hello, World!' - assert_failure "No such core module: $_GO_CORE_DIR/lib/foobar" +@test "$SUITE: create_module_test_stub to stub out builtin module" { + @go.create_test_go_script '. "$_GO_USE_MODULES" "$@"' + @go.create_module_test_stub 'log' 'printf "INJECTED\n"' + run "$TEST_GO_SCRIPT" log + assert_success 'INJECTED' }