diff --git a/go-core.bash b/go-core.bash index 2063c45..9906b28 100755 --- a/go-core.bash +++ b/go-core.bash @@ -26,7 +26,8 @@ # https://mike-bland.com/ # https://github.com/mbland -if [[ "${BASH_VERSINFO[0]}" -lt '3' || "${BASH_VERSINFO[1]}" -lt '2' ]]; then +if [[ "${BASH_VERSINFO[0]}" -lt '3' || + ( "${BASH_VERSINFO[0]}" -eq '3' && "${BASH_VERSINFO[1]}" -lt '2' ) ]]; then printf "This module requires bash version 3.2 or greater:\n %s %s\n" \ "$BASH" "$BASH_VERSION" exit 1 diff --git a/lib/bats/helpers b/lib/bats/helpers index 7c3fad0..473e2b4 100644 --- a/lib/bats/helpers +++ b/lib/bats/helpers @@ -313,11 +313,11 @@ stub_program_in_path() { in_process='true' shift fi + create_bats_test_script "${BATS_TEST_BINDIR#$BATS_TEST_ROOTDIR/}/$1" "${@:2}" if [[ ! "$PATH" =~ $bindir_pattern ]]; then export PATH="$BATS_TEST_BINDIR:$PATH" fi - create_bats_test_script "${BATS_TEST_BINDIR#$BATS_TEST_ROOTDIR/}/$1" "${@:2}" if [[ -n "$in_process" ]]; then hash "$1" diff --git a/lib/testing/environment b/lib/testing/environment index ad8124a..250b0ad 100644 --- a/lib/testing/environment +++ b/lib/testing/environment @@ -120,8 +120,19 @@ test-go() { # ...: Arguments passed directly to `@go.compgen` @go.test_compgen() { set "$DISABLE_BATS_SHELL_OPTIONS" + local completions + local err_args + . "$_GO_USE_MODULES" 'complete' 'strings' - @go.split $'\n' "$(@go.compgen "${@:2}")" "$1" + completions="$(@go.compgen "${@:2}")" + + if [[ "$?" -ne '0' || -z "$completions" ]]; then + printf -v 'err_args' ' "%s"' "${@:2}" + printf 'compgen failed or results were empty:%s\n' "$err_args" >&2 + restore_bats_shell_options '1' + return + fi + @go.split $'\n' "$completions" "$1" restore_bats_shell_options "$?" } diff --git a/libexec/new b/libexec/new new file mode 100755 index 0000000..5c0e52f --- /dev/null +++ b/libexec/new @@ -0,0 +1,297 @@ +#! /usr/bin/env bash +# +# Generate a new command script, module, test, or other text file +# +# Usage: +# To generate an arbitrary text file: +# {{go}} {{cmd}} --type [lines...] +# +# To generate a new command script in `_GO_SCRIPTS_DIR`: +# {{go}} {{cmd}} --command [...] +# +# To generate a new internal module in `_GO_SCRIPTS_DIR/lib`: +# {{go}} {{cmd}} --internal +# +# To generate a new public module in `_GO_ROOTDIR/lib`: +# {{go}} {{cmd}} --public +# +# To generate a new Bats test file in `_GO_TEST_DIR`: +# {{go}} {{cmd}} --test +# +# Where: +# +# Very brief description of the file type (can be empty) +# Path to the new file +# [lines...] Optional list of lines to add to the file +# Permissions to set for the new file +# Command script name +# Subcommand script name +# Path to module file relative to `_GO_*DIR/lib` +# Path to module file relative to `_GO_TEST_DIR` +# +# Any component of the target file path that does not yet exist will be created. +# +# If the `EDITOR` environment variable is defined, the newly-generated file (or +# files, possible with `--command`) will be opened for editing with `--command`, +# `--internal`, `--public`, or `--test`. It will be opened for other files when +# the list of `lines...` is empty. +# +# When invoking the `--command` form, this command will also generate a new +# script for any name preceding the final `` that does not yet +# correspond to an existing parent command script. These parent commands will +# invoke `@go.show_subcommands` from the core `subcommands` module by default. + +_@go.new_tab_completions() { + local word_index="$1" + local mode="$2" + shift 2 + + if [[ "$word_index" -eq '0' ]]; then + printf -- '--command --internal --public --test --type' + return + fi + ((--word_index)) + + case "$mode" in + --command) + if [[ "$word_index" -eq '0' ]]; then + _@go.source_builtin 'commands' "$_GO_SCRIPTS_DIR" + else + . "$_GO_CORE_DIR/lib/internal/complete" + _@go.complete_command_path "$word_index" "$@" + fi + return + ;; + --internal) + if [[ "$word_index" -ne '0' ]] || ! cd "$_GO_SCRIPTS_DIR/lib"; then + return 1 + fi + ;; + --public) + if [[ "$word_index" -ne '0' ]] || ! cd "$_GO_ROOTDIR/lib"; then + return 1 + fi + ;; + --test) + if [[ "$word_index" -ne '0' ]] || ! cd "$_GO_ROOTDIR/$_GO_TEST_DIR"; then + return 1 + fi + ;; + --type) + if [[ "$word_index" -ne '1' ]]; then + return 1 + fi + shift + ;; + *) + return 1 + ;; + esac + @go.compgen -f -- "$1" +} + +_@go.new_file() { + local file_type="$1" + local file_path="$2" + local permissions="$3" + shift 3 + local relpath="$file_path" + local parent_dir + local permissions_pattern='([0-7][0-7][0-7]|[ugo]{1,3}[+-][rwx]{1,3})' + + if [[ -z "$_GO_STANDALONE" ]]; then + relpath="${relpath#$_GO_ROOTDIR/}" + fi + + parent_dir="${relpath%/*}" + if [[ "$parent_dir" == "$relpath" ]]; then + parent_dir="$PWD" + fi + + if [[ -n "$file_type" ]]; then + file_type+=' ' + fi + + if [[ -z "$file_path" ]]; then + @go.printf 'No %sfile path specified.\n' "$file_type" >&2 + return 1 + elif [[ ! "$permissions" =~ $permissions_pattern ]]; then + @go.printf 'Invalid permissions specification "%s" for %sfile: %s\n' \ + "$permissions" "$file_type" "$relpath" >&2 + return 1 + elif [[ ! -d "$parent_dir" ]] && ! mkdir -p "$parent_dir"; then + @go.printf "Couldn't create parent directory for new %sfile: %s\n" \ + "$file_type" "$relpath" >&2 + return 1 + elif [[ -f "$file_path" ]]; then + @go.printf '%sfile already exists: %s\n' "$file_type" "$relpath" >&2 + return 1 + elif ! printf -- '%s\n' "$@" >"$file_path"; then + @go.printf 'Failed to create new %sfile: %s\n' "$file_type" "$relpath" >&2 + return 1 + elif ! chmod "$permissions" "$file_path"; then + @go.printf 'Failed to set permissions for new %sfile to "%s": %s\n' \ + "$file_type" "$permissions" "$relpath" >&2 + return 1 + fi +} + +_@go.new_command_script() { + local cmd="$1" + local cmd_path="$2" + local is_last_cmd="$3" + local script_impl=('#! /usr/bin/env bash' + '#' + '# Short description of the {{cmd}} command' '') + + if [[ -n "$is_last_cmd" ]]; then + script_impl+=("_$cmd() {" + ' :' + '}' + '' + "_$cmd \"\$@\"") + else + script_impl+=(". \"\$_GO_USE_MODULES\" 'subcommands'" + '' + '@go.show_subcommands') + fi + _@go.new_file "command script" "$cmd_path" '755' "${script_impl[@]}" +} + +_@go.new_command_scripts() { + local cmd + local cmd_path + local parent_dir="$_GO_SCRIPTS_DIR" + local new_scripts=() + local is_last_cmd + local i=0 + + if [[ "$#" -eq '0' ]]; then + printf 'No command script name specified.\n' >&2 + return 1 + fi + + for cmd in "$@"; do + cmd_path="$parent_dir/$cmd" + parent_dir="$cmd_path.d" + + if [[ "$((++i))" -eq "$#" ]]; then + is_last_cmd='true' + elif [[ -f "$cmd_path" ]]; then + continue + fi + new_scripts+=("$cmd_path") + + if ! _@go.new_command_script "$cmd" "$cmd_path" "$is_last_cmd"; then + return 1 + fi + done + + if command -v "$EDITOR" >/dev/null; then + "$EDITOR" "${new_scripts[@]}" + fi +} + +_@go.new_module() { + local module_path="$1" + local module_relpath="${module_path#*/lib/}" + local module_type + local impl=('#! /usr/bin/env bash' + '#' + "# Short description of the $module_relpath module" + '#' + '# Exports:' + '# func_name' + '# Short description of the func_name function') + + case "${module_path%%/lib/*}" in + $_GO_SCRIPTS_DIR) + module_type='internal module' + ;; + $_GO_ROOTDIR) + module_type='public module' + ;; + esac + + if ! _@go.new_file "$module_type" "$module_path" '644' "${impl[@]}"; then + return 1 + elif command -v "$EDITOR" >/dev/null; then + "$EDITOR" "$module_path" + fi +} + +_@go.new_test() { + local test_path="${1%.bats}.bats" + local test_relpath="${test_path#$_GO_ROOTDIR/$_GO_TEST_DIR/}" + local parent_dir="${test_relpath%/*}" + local impl + + if [[ -n "$parent_dir" ]]; then + parent_dir="${parent_dir//[^\/]}/" + fi + + impl=('#! /usr/bin/env bats' + '' + "load ${parent_dir//\//../}environment" + '' + 'setup() {' + ' test_filter' + ' @go.create_test_go_script' + '}' + '' + 'teardown() {' + ' @go.remove_test_go_rootdir' + '}' + '' + '@test "$SUITE: short description of your first test case" {' + '}') + + if ! _@go.new_file "Bats test" "$test_path" '644' "${impl[@]}"; then + return 1 + elif command -v "$EDITOR" >/dev/null; then + "$EDITOR" "$test_path" + fi +} + +_@go.new() { + local mode="$1" + + if [[ "$#" -eq '0' ]]; then + @go 'help' "${_GO_CMD_NAME[@]}" >&2 + return 1 + fi + shift + + case "$mode" in + --complete) + # Tab completions + _@go.new_tab_completions "$@" + return + ;; + --command) + _@go.new_command_scripts "$@" + ;; + --internal) + _@go.new_module "$_GO_SCRIPTS_DIR/lib/$1" + ;; + --public) + _@go.new_module "$_GO_ROOTDIR/lib/$1" + ;; + --test) + _@go.new_test "$_GO_ROOTDIR/$_GO_TEST_DIR/$1" + ;; + --type) + if ! _@go.new_file "$1" "$2" "${@:3}"; then + return 1 + elif [[ "$#" -le '3' ]] && command -v "$EDITOR" >/dev/null; then + "$EDITOR" "$2" + fi + ;; + *) + printf 'The first argument is "%s", but must be one of:\n %s\n' \ + "$mode" '--command --internal --public --test --type' >&2 + return 1 + esac +} + +_@go.new "$@" diff --git a/tests/bats-helpers.bats b/tests/bats-helpers.bats index d8a3988..2274f38 100644 --- a/tests/bats-helpers.bats +++ b/tests/bats-helpers.bats @@ -240,11 +240,11 @@ __check_dirs_exist() { local bats_bindir_pattern="^${BATS_TEST_BINDIR}:" fail_if matches "$bats_bindir_pattern" "$PATH" - stub_program_in_path 'git' 'echo "$@"' + stub_program_in_path 'chmod' 'echo "$@"' assert_matches "$bats_bindir_pattern" "$PATH" - run git Hello, World! - assert_success 'Hello, World!' + run chmod ugo+rwx foo.txt + assert_success 'ugo+rwx foo.txt' } @test "$SUITE: {stub,restore}_program_in_path for testing in-process function" { diff --git a/tests/new.bats b/tests/new.bats new file mode 100644 index 0000000..b81fc81 --- /dev/null +++ b/tests/new.bats @@ -0,0 +1,439 @@ +#! /usr/bin/env bats + +load environment + +setup() { + test_filter + @go.create_test_go_script '@go "$@"' +} + +teardown() { + @go.remove_test_go_rootdir +} + +# Will list the file names passed as arguments for tests that check that +# `EDITOR` gets called. Tests that check that it doesn't get called should +# see no file names output. +test_editor() { + printf -- "EDITING: %s\n" "$@" +} +export -f test_editor +export EDITOR='test_editor' + +assert_command_script_is_executable() { + set "$DISABLE_BATS_SHELL_OPTIONS" + local cmd_script_path="$1" + if [[ ! -x "$TEST_GO_SCRIPTS_DIR/$cmd_script_path" ]]; then + printf 'Failed to make command script executable: %s\n' \ + "$TEST_GO_SCRIPTS_DIR/$cmd_script_path" >&2 + restore_bats_shell_options '1' + else + restore_bats_shell_options + fi +} + +@test "$SUITE: tab complete first argument" { + local flags=('--command' '--internal' '--public' '--test' '--type') + run "$TEST_GO_SCRIPT" complete 1 new '' + assert_success "${flags[@]}" + + run "$TEST_GO_SCRIPT" complete 1 new '-' + assert_success "${flags[@]}" + + run "$TEST_GO_SCRIPT" complete 1 new '--' + assert_success "${flags[@]}" + + run "$TEST_GO_SCRIPT" complete 1 new '--t' + assert_success '--test' '--type' +} + +@test "$SUITE: tab complete fails for unknown and incomplete flags" { + run "$TEST_GO_SCRIPT" complete 1 new '--foo' + assert_failure '' + + run "$TEST_GO_SCRIPT" complete 2 new '--foo' 'bar' + assert_failure '' + + @go.create_test_command_script 'foobar' + run "$TEST_GO_SCRIPT" complete 2 new '--comman' 'fooba' + assert_failure '' +} + +@test "$SUITE: tab complete --command" { + run "$TEST_GO_SCRIPT" complete 1 new '--c' + assert_success '--command ' + + @go.create_parent_and_subcommands foo bar baz + @go.create_test_command_script quux + @go.create_test_command_script xyzzy + + run "$TEST_GO_SCRIPT" complete 2 new '--command' + assert_success 'foo' 'quux' 'xyzzy' + + run "$TEST_GO_SCRIPT" complete 2 new '--command' 'f' + assert_success 'foo ' + + run "$TEST_GO_SCRIPT" complete 3 new '--command' 'foo' 'b' + assert_success 'bar' 'baz' + + run "$TEST_GO_SCRIPT" complete 4 new '--command' 'foo' 'bar' + assert_failure '' +} + +@test "$SUITE: tab complete --internal" { + run "$TEST_GO_SCRIPT" complete 1 new '--i' + assert_success '--internal ' + + local internal_modules + internal_modules=("$TEST_GO_SCRIPTS_DIR/lib/"{bar,baz,foo}) + mkdir -p "$TEST_GO_SCRIPTS_DIR/lib/" + touch "${internal_modules[@]}" + + local expected + @go.test_compgen 'expected' -f "$TEST_GO_SCRIPTS_DIR/lib/" + + run "$TEST_GO_SCRIPT" complete 2 new '--internal' + assert_success "${expected[@]#$TEST_GO_SCRIPTS_DIR/lib/}" + + run "$TEST_GO_SCRIPT" complete 2 new '--internal' 'f' + assert_success 'foo ' + + run "$TEST_GO_SCRIPT" complete 3 new '--internal' 'foo' + assert_failure '' +} + +@test "$SUITE: tab complete --public" { + run "$TEST_GO_SCRIPT" complete 1 new '--p' + assert_success '--public ' + + local public_modules + public_modules=("$TEST_GO_ROOTDIR/lib/"{plugh,quux,xyzzy}) + mkdir -p "$TEST_GO_ROOTDIR/lib/" + touch "${public_modules[@]}" + + local expected + @go.test_compgen 'expected' -f "$TEST_GO_ROOTDIR/lib/" + + run "$TEST_GO_SCRIPT" complete 2 new '--public' + assert_success "${expected[@]#$TEST_GO_ROOTDIR/lib/}" + + run "$TEST_GO_SCRIPT" complete 2 new '--public' 'q' + assert_success 'quux ' + + run "$TEST_GO_SCRIPT" complete 3 new '--public' 'quux' + assert_failure '' +} + +@test "$SUITE: tab complete --test" { + run "$TEST_GO_SCRIPT" complete 1 new '--te' + assert_success '--test ' + + local test_files + test_files=("$TEST_GO_ROOTDIR/tests/"{aimfiz,blorple,frotz}.bats) + mkdir -p "$TEST_GO_ROOTDIR/tests/" + touch "${test_files[@]}" + + local expected + @go.test_compgen 'expected' -f "$TEST_GO_ROOTDIR/$_GO_TEST_DIR/" + + run "$TEST_GO_SCRIPT" complete 2 new '--test' + assert_success "${expected[@]#$TEST_GO_ROOTDIR/$_GO_TEST_DIR/}" + + run "$TEST_GO_SCRIPT" complete 2 new '--test' 'f' + assert_success 'frotz.bats ' + + run "$TEST_GO_SCRIPT" complete 3 new '--test' 'frotz.bats' + assert_failure '' +} + +@test "$SUITE: tab complete --type" { + run "$TEST_GO_SCRIPT" complete 1 new '--ty' + assert_success '--type ' + + local text_files + text_files=("$TEST_GO_ROOTDIR/gue/"{dungeonmaster,thief,wizard}.txt) + mkdir -p "$TEST_GO_ROOTDIR/gue" + touch "${text_files[@]}" + + local expected + @go.test_compgen 'expected' -f "$TEST_GO_ROOTDIR/g" + + run "$TEST_GO_SCRIPT" complete 2 new '--type' + assert_failure '' + + run "$TEST_GO_SCRIPT" complete 3 new '--type' 'adversary' 'g' + assert_success "${expected[@]#$TEST_GO_ROOTDIR/}" + + run "$TEST_GO_SCRIPT" complete 3 new '--type' 'adversary' 'gu' + assert_success 'gue/' + + @go.test_compgen 'expected' -f "$TEST_GO_ROOTDIR/gue/" + run "$TEST_GO_SCRIPT" complete 3 new '--type' 'adversary' 'gue/' + assert_success "${expected[@]#$TEST_GO_ROOTDIR/}" + + run "$TEST_GO_SCRIPT" complete 3 new '--type' 'adversary' 'gue/t' + assert_success 'gue/thief.txt ' + + run "$TEST_GO_SCRIPT" complete 4 new '--type' 'adversary' 'gue/thief.txt' + assert_failure '' +} + +@test "$SUITE: show help with exit with error when no args" { + run "$TEST_GO_SCRIPT" new + assert_failure + assert_line_matches '0' "^$TEST_GO_SCRIPT new - Generate a new .* file\$" +} + +@test "$SUITE: exit with error on invalid first argument/mode flag" { + run "$TEST_GO_SCRIPT" new foo bar + assert_failure 'The first argument is "foo", but must be one of:' \ + ' --command --internal --public --test --type' +} + +@test "$SUITE: creating a file without lines opens EDITOR if found" { + run "$TEST_GO_SCRIPT" new --type '' foo.txt 644 + assert_success 'EDITING: foo.txt' + assert_file_equals "$TEST_GO_ROOTDIR/foo.txt" '' + + EDITOR= run "$TEST_GO_SCRIPT" new --type '' bar/baz.txt 'ugo+rwx' + assert_success '' + assert_file_equals "$TEST_GO_ROOTDIR/bar/baz.txt" '' +} + +@test "$SUITE: creating a file with lines doesn't open EDITOR" { + run "$TEST_GO_SCRIPT" new --type '' foo.txt 644 bar baz quux + assert_success '' + assert_file_equals "$TEST_GO_ROOTDIR/foo.txt" 'bar' 'baz' 'quux' +} + +@test "$SUITE: error when creating a file without specifying the path" { + run "$TEST_GO_SCRIPT" new --type '' + assert_failure 'No file path specified.' + + run "$TEST_GO_SCRIPT" new --type 'foo' + assert_failure 'No foo file path specified.' +} + +@test "$SUITE: error when creating a file with invalid permission spec" { + run "$TEST_GO_SCRIPT" new --type '' 'foo.txt' 'rwx-ugo' + assert_failure 'Invalid permissions specification "rwx-ugo" for file: foo.txt' + + run "$TEST_GO_SCRIPT" new --type 'foo' 'foo.txt' '800' + assert_failure 'Invalid permissions specification "800" for foo file: foo.txt' +} + +@test "$SUITE: error if creating parent directory fails" { + stub_program_in_path 'mkdir' 'printf "ARG: %s\n" "$@"' 'exit 1' + + run "$TEST_GO_SCRIPT" new --type '' foo/bar.txt 644 + assert_failure 'ARG: -p' 'ARG: foo' \ + "Couldn't create parent directory for new file: foo/bar.txt" + + run "$TEST_GO_SCRIPT" new --type 'foo' foo/bar.txt 'ugo+rwx' + assert_failure 'ARG: -p' 'ARG: foo' \ + "Couldn't create parent directory for new foo file: foo/bar.txt" +} + +@test "$SUITE: error if file already exists" { + mkdir -p "$TEST_GO_ROOTDIR/foo" + touch "$TEST_GO_ROOTDIR/foo/bar.txt" + + run "$TEST_GO_SCRIPT" new --type '' foo/bar.txt 644 + assert_failure 'file already exists: foo/bar.txt' + + run "$TEST_GO_SCRIPT" new --type 'foo' foo/bar.txt 'ugo+rwx' + assert_failure 'foo file already exists: foo/bar.txt' +} + +@test "$SUITE: error if printing to file fails" { + stub_program_in_path 'mkdir' 'printf "ARG: %s\n" "$@"' \ + 'printf "DIR NOT CREATED\n"' + + local sys_err_regex="^$_GO_CORE_DIR/libexec/new: line [1-9][0-9]*: " + sys_err_regex+='foo/bar.txt: No such file or directory$' + + local expected=('^ARG: -p$' '^ARG: foo$' '^DIR NOT CREATED$' "$sys_err_regex") + + run "$TEST_GO_SCRIPT" new --type '' foo/bar.txt 644 + assert_failure + assert_lines_match "${expected[@]}" \ + '^Failed to create new file: foo/bar.txt$' + + run "$TEST_GO_SCRIPT" new --type 'foo' foo/bar.txt 'ugo+rwx' + assert_lines_match "${expected[@]}" \ + '^Failed to create new foo file: foo/bar.txt$' +} + +@test "$SUITE: error if setting permissions fails" { + stub_program_in_path 'chmod' 'printf "ARG: %s\n" "$@"' 'exit 1' + + run "$TEST_GO_SCRIPT" new --type '' foo/bar.txt 644 + assert_failure 'ARG: 644' 'ARG: foo/bar.txt' \ + 'Failed to set permissions for new file to "644": foo/bar.txt' + + rm "$TEST_GO_ROOTDIR/foo/bar.txt" + run "$TEST_GO_SCRIPT" new --type 'foo' foo/bar.txt 'ugo+rwx' + assert_failure 'ARG: ugo+rwx' 'ARG: foo/bar.txt' \ + 'Failed to set permissions for new foo file to "ugo+rwx": foo/bar.txt' +} + +@test "$SUITE: error messages don't trim _GO_ROOTDIR in _GO_STANDALONE mode" { + mkdir -p "$TEST_GO_ROOTDIR/foo" + touch "$TEST_GO_ROOTDIR/foo/bar.txt" + + _GO_STANDALONE='true' run "$TEST_GO_SCRIPT" new --type '' \ + "$TEST_GO_ROOTDIR/foo/bar.txt" 644 + assert_failure "file already exists: $TEST_GO_ROOTDIR/foo/bar.txt" +} + +@test "$SUITE: new command script" { + run "$TEST_GO_SCRIPT" new --command foo + assert_success "EDITING: $TEST_GO_SCRIPTS_DIR/foo" + assert_file_equals "$TEST_GO_SCRIPTS_DIR/foo" \ + '#! /usr/bin/env bash' \ + '#' \ + '# Short description of the {{cmd}} command' \ + '' \ + '_foo() {' \ + ' :' \ + '}' \ + '' \ + '_foo "$@"' + assert_command_script_is_executable 'foo' + + rm "$TEST_GO_SCRIPTS_DIR/foo" + EDITOR= run "$TEST_GO_SCRIPT" new --command foo + assert_success '' + assert_file_matches "$TEST_GO_SCRIPTS_DIR/foo" $'\n_foo "\$@"' +} + +@test "$SUITE: new subcommand script" { + run "$TEST_GO_SCRIPT" new --command foo + assert_success "EDITING: $TEST_GO_SCRIPTS_DIR/foo" + assert_file_matches "$TEST_GO_SCRIPTS_DIR/foo" $'\n_foo\(\) \{\n' + assert_command_script_is_executable 'foo' + + run "$TEST_GO_SCRIPT" new --command foo bar + assert_success "EDITING: $TEST_GO_SCRIPTS_DIR/foo.d/bar" + assert_file_matches "$TEST_GO_SCRIPTS_DIR/foo.d/bar" $'\n_bar\(\) \{\n' + assert_command_script_is_executable 'foo.d/bar' +} + +@test "$SUITE: new command and subcommand scripts" { + run "$TEST_GO_SCRIPT" new --command foo bar baz + assert_success "EDITING: $TEST_GO_SCRIPTS_DIR/foo" \ + "EDITING: $TEST_GO_SCRIPTS_DIR/foo.d/bar" \ + "EDITING: $TEST_GO_SCRIPTS_DIR/foo.d/bar.d/baz" + + assert_file_equals "$TEST_GO_SCRIPTS_DIR/foo" \ + '#! /usr/bin/env bash' \ + '#' \ + '# Short description of the {{cmd}} command' \ + '' \ + ". \"\$_GO_USE_MODULES\" 'subcommands'" \ + '' \ + '@go.show_subcommands' + assert_file_matches "$TEST_GO_SCRIPTS_DIR/foo.d/bar" '@go.show_subcommands' + assert_file_matches "$TEST_GO_SCRIPTS_DIR/foo.d/bar.d/baz" $'\n_baz\(\) \{\n' + + assert_command_script_is_executable 'foo' + assert_command_script_is_executable 'foo.d/bar' + assert_command_script_is_executable 'foo.d/bar.d/baz' +} + +@test "$SUITE: --command fails if no script specified" { + run "$TEST_GO_SCRIPT" new --command + assert_failure 'No command script name specified.' +} + +@test "$SUITE: --command fails if script already exists" { + run "$TEST_GO_SCRIPT" new --command foo bar baz + assert_success "EDITING: $TEST_GO_SCRIPTS_DIR/foo" \ + "EDITING: $TEST_GO_SCRIPTS_DIR/foo.d/bar" \ + "EDITING: $TEST_GO_SCRIPTS_DIR/foo.d/bar.d/baz" + run "$TEST_GO_SCRIPT" new --command foo bar baz + + local failing_path="$TEST_GO_SCRIPTS_RELATIVE_DIR/foo.d/bar.d/baz" + assert_failure "command script file already exists: $failing_path" +} + +@test "$SUITE: --internal creates new internal module" { + run "$TEST_GO_SCRIPT" new --internal foo + assert_success "EDITING: $TEST_GO_SCRIPTS_DIR/lib/foo" + assert_file_equals "$TEST_GO_SCRIPTS_DIR/lib/foo" \ + '#! /usr/bin/env bash' \ + '#' \ + '# Short description of the foo module' \ + '#' \ + '# Exports:' \ + '# func_name' \ + '# Short description of the func_name function' + + rm "$TEST_GO_SCRIPTS_DIR/lib/foo" + EDITOR= run "$TEST_GO_SCRIPT" new --internal foo + assert_success '' + assert_file_matches "$TEST_GO_SCRIPTS_DIR/lib/foo" \ + $'\n# Short description of the foo module\n' +} + +@test "$SUITE: --internal fails if internal module already exists" { + run "$TEST_GO_SCRIPT" new --internal foo + assert_success "EDITING: $TEST_GO_SCRIPTS_DIR/lib/foo" + run "$TEST_GO_SCRIPT" new --internal foo + assert_failure \ + "internal module file already exists: $TEST_GO_SCRIPTS_RELATIVE_DIR/lib/foo" +} + +@test "$SUITE: --public creates new public module" { + run "$TEST_GO_SCRIPT" new --public foo/bar/baz + assert_success "EDITING: $TEST_GO_ROOTDIR/lib/foo/bar/baz" + assert_file_matches "$TEST_GO_ROOTDIR/lib/foo/bar/baz" \ + $'\n# Short description of the foo/bar/baz module\n' + + rm "$TEST_GO_ROOTDIR/lib/foo/bar/baz" + EDITOR= run "$TEST_GO_SCRIPT" new --public foo/bar/baz + assert_success '' + assert_file_matches "$TEST_GO_ROOTDIR/lib/foo/bar/baz" \ + $'\n# Short description of the foo/bar/baz module\n' +} + +@test "$SUITE: --public fails if public module already exists" { + run "$TEST_GO_SCRIPT" new --public foo/bar/baz + assert_success "EDITING: $TEST_GO_ROOTDIR/lib/foo/bar/baz" + run "$TEST_GO_SCRIPT" new --public foo/bar/baz + assert_failure 'public module file already exists: lib/foo/bar/baz' +} + +@test "$SUITE: --test creates new Bats test file" { + run "$TEST_GO_SCRIPT" new --test foo/bar/baz + assert_success "EDITING: $TEST_GO_ROOTDIR/$_GO_TEST_DIR/foo/bar/baz.bats" + assert_file_equals "$TEST_GO_ROOTDIR/$_GO_TEST_DIR/foo/bar/baz.bats" \ + '#! /usr/bin/env bats' \ + '' \ + 'load ../../environment' \ + '' \ + 'setup() {' \ + ' test_filter' \ + ' @go.create_test_go_script' \ + '}' \ + '' \ + 'teardown() {' \ + ' @go.remove_test_go_rootdir' \ + '}' \ + '' \ + '@test "$SUITE: short description of your first test case" {' \ + '}' + + rm "$TEST_GO_ROOTDIR/$_GO_TEST_DIR/foo/bar/baz.bats" + EDITOR= run "$TEST_GO_SCRIPT" new --test foo/bar/baz + assert_success '' + assert_file_matches "$TEST_GO_ROOTDIR/$_GO_TEST_DIR/foo/bar/baz.bats" \ + $'\n@test "\$SUITE: short description of your first test case" \{\n' +} + +@test "$SUITE: --test fails if public module already exists" { + run "$TEST_GO_SCRIPT" new --test foo/bar/baz + assert_success "EDITING: $TEST_GO_ROOTDIR/$_GO_TEST_DIR/foo/bar/baz.bats" + run "$TEST_GO_SCRIPT" new --test foo/bar/baz + assert_failure "Bats test file already exists: $_GO_TEST_DIR/foo/bar/baz.bats" +} diff --git a/tests/testing/environment.bats b/tests/testing/environment.bats index 1f689f4..8311cdd 100644 --- a/tests/testing/environment.bats +++ b/tests/testing/environment.bats @@ -93,33 +93,3 @@ teardown() { run test-go assert_success '_GO_CMD: test-go' } - -setup_go_test_compgen() { - local item - - mkdir -p "$TEST_GO_ROOTDIR/lib" - printf 'foo' >"$TEST_GO_ROOTDIR/lib/foo" - printf 'bar' >"$TEST_GO_ROOTDIR/lib/bar" - printf 'baz' >"$TEST_GO_ROOTDIR/lib/baz" - - . "$_GO_USE_MODULES" 'complete' - while IFS= read -r item; do - __expected+=("${item#$TEST_GO_ROOTDIR/}") - done < <(@go.compgen -f -- "$TEST_GO_ROOTDIR/lib/") -} - -@test "$SUITE: @go.test_compgen" { - set "$DISABLE_BATS_SHELL_OPTIONS" - local __expected=() - setup_go_test_compgen - restore_bats_shell_options "$?" - - export -f @go.test_compgen - @go.create_test_go_script \ - 'declare results=()' \ - '@go.test_compgen "results" -f -- lib/' \ - 'printf "%s\n" "${results[@]}"' - - run "$TEST_GO_SCRIPT" - assert_success "${__expected[@]}" -} diff --git a/tests/testing/environment/test-compgen.bats b/tests/testing/environment/test-compgen.bats new file mode 100644 index 0000000..793bd77 --- /dev/null +++ b/tests/testing/environment/test-compgen.bats @@ -0,0 +1,56 @@ +#! /usr/bin/env bats + +load ../../environment + +EXPECTED=() + +setup() { + test_filter + @go.create_test_go_script + export -f @go.test_compgen + + set "$DISABLE_BATS_SHELL_OPTIONS" + setup_go_test_compgen + restore_bats_shell_options "$?" + + @go.create_test_go_script \ + 'declare results=()' \ + 'if ! @go.test_compgen "results" "$@"; then' \ + ' exit 1' \ + 'fi' \ + 'printf "%s\n" "${results[@]}"' +} + +teardown() { + @go.remove_test_go_rootdir +} + +setup_go_test_compgen() { + local item + + mkdir -p "$TEST_GO_ROOTDIR/lib" + printf 'foo' >"$TEST_GO_ROOTDIR/lib/foo" + printf 'bar' >"$TEST_GO_ROOTDIR/lib/bar" + printf 'baz' >"$TEST_GO_ROOTDIR/lib/baz" + + . "$_GO_USE_MODULES" 'complete' + while IFS= read -r item; do + EXPECTED+=("${item#$TEST_GO_ROOTDIR/}") + done < <(@go.compgen -f -- "$TEST_GO_ROOTDIR/lib/") +} + +@test "$SUITE: completion succeeds" { + run "$TEST_GO_SCRIPT" -f -- lib/ + assert_success "${EXPECTED[@]}" +} + +@test "$SUITE: completion fails" { + run "$TEST_GO_SCRIPT" -f -- lib/nonexistent + assert_failure \ + 'compgen failed or results were empty: "-f" "--" "lib/nonexistent"' +} + +@test "$SUITE: fails if completions are empty" { + run "$TEST_GO_SCRIPT" lib/nonexistent + assert_failure 'compgen failed or results were empty: "lib/nonexistent"' +}