Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/bats-core/bats-support.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZoltan Tombol <zoltan.tombol@gmail.com>2016-11-22 02:10:22 +0300
committerZoltan Tombol <zoltan.tombol@gmail.com>2016-11-22 02:10:22 +0300
commit57d863c679fd8ae0563be538ab81b53712157b9f (patch)
tree52c783564af673059876afb500807bf0006e45f4
parentd0a131831c487a1f1141e76d3ab386c89642cdff (diff)
Add batslib_is_caller()
-rw-r--r--CHANGELOG.md9
-rw-r--r--README.md61
-rw-r--r--load.bash1
-rw-r--r--src/lang.bash73
-rwxr-xr-xtest/52-lang-10-batslib_is_caller.bats88
5 files changed, 232 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ac6fbc..4274168 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+## [Unreleased]
+
+### Added
+
+- Restricting invocation to specific locations with
+ `batslib_is_caller()`
+
+
## [0.2.0] - 2016-03-22
### Added
@@ -34,4 +42,5 @@ This project adheres to [Semantic Versioning](http://semver.org/).
`batslib_get_max_single_line_key_width()`
+[Unreleased]: https://github.com/ztombol/bats-support/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/ztombol/bats-support/compare/v0.1.0...v0.2.0
diff --git a/README.md b/README.md
index cd3fee1..71c02ba 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@ test helper libraries written for [Bats][bats].
Features:
- [error reporting](#error-reporting)
- [output formatting](#output-formatting)
+- [language tools](#language-and-execution)
See the [shared documentation][bats-docs] to learn how to install and
load this library.
@@ -121,6 +122,66 @@ actual (3 lines):
--
```
+## Language and Execution
+
+### Restricting invocation to specific locations
+
+Sometimes a helper may work properly only when called from a certain
+location. Because it depends on variables to be set or some other side
+effect.
+
+A good example is cleaning up temporary files only if the test has
+succeeded. The outcome of a test is only available in `teardown`. Thus,
+to avoid programming mistakes, it makes sense to restrict such a
+clean-up helper to that function.
+
+`batslib_is_caller` checks the call stack and returns `0` if the caller
+was invoked from a given function, and `1` otherwise. This function
+becomes really useful with the `--indirect` option, which allows calls
+through intermediate functions, e.g. the calling function may be called
+from a function that was called from the given function.
+
+Staying with the example above, the following code snippet implements a
+helper that is restricted to `teardown` or any function called
+indirectly from it.
+
+```shell
+clean_up() {
+ # Check caller.
+ if batslib_is_caller --indirect 'teardown'; then
+ echo "Must be called from \`teardown'" \
+ | batslib_decorate 'ERROR: clean_up' \
+ | fail
+ return $?
+ fi
+
+ # Body goes here...
+}
+```
+
+In some cases a helper may be called from multiple locations. For
+example, a logging function that uses the test name, description or
+number, information only available in `setup`, `@test` or `teardown`, to
+distinguish entries. The following snippet implements this restriction.
+
+```shell
+log_test() {
+ # Check caller.
+ if ! ( batslib_is_caller --indirect 'setup' \
+ || batslib_is_caller --indirect "$BATS_TEST_NAME" \
+ || batslib_is_caller --indirect 'teardown' )
+ then
+ echo "Must be called from \`setup', \`@test' or \`teardown'" \
+ | batslib_decorate 'ERROR: log_test' \
+ | fail
+ return $?
+ fi
+
+ # Body goes here...
+}
+```
+
+
<!-- REFERENCES -->
[bats]: https://github.com/sstephenson/bats
diff --git a/load.bash b/load.bash
index 0d4a5ac..0727aeb 100644
--- a/load.bash
+++ b/load.bash
@@ -1,2 +1,3 @@
source "$(dirname "${BASH_SOURCE[0]}")/src/output.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/error.bash"
+source "$(dirname "${BASH_SOURCE[0]}")/src/lang.bash"
diff --git a/src/lang.bash b/src/lang.bash
new file mode 100644
index 0000000..c57e299
--- /dev/null
+++ b/src/lang.bash
@@ -0,0 +1,73 @@
+#
+# bats-util - Various auxiliary functions for Bats
+#
+# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
+#
+# To the extent possible under law, the author(s) have dedicated all
+# copyright and related and neighboring rights to this software to the
+# public domain worldwide. This software is distributed without any
+# warranty.
+#
+# You should have received a copy of the CC0 Public Domain Dedication
+# along with this software. If not, see
+# <http://creativecommons.org/publicdomain/zero/1.0/>.
+#
+
+#
+# lang.bash
+# ---------
+#
+# Bash language and execution related functions. Used by public helper
+# functions.
+#
+
+# Check whether the calling function was called from a given function.
+#
+# By default, direct invocation is checked. The function succeeds if the
+# calling function was called directly from the given function. In other
+# words, if the given function is the next element on the call stack.
+#
+# When `--indirect' is specified, indirect invocation is checked. The
+# function succeeds if the calling function was called from the given
+# function with any number of intermediate calls. In other words, if the
+# given function can be found somewhere on the call stack.
+#
+# Direct invocation is a form of indirect invocation with zero
+# intermediate calls.
+#
+# Globals:
+# FUNCNAME
+# Options:
+# -i, --indirect - check indirect invocation
+# Arguments:
+# $1 - calling function's name
+# Returns:
+# 0 - current function was called from the given function
+# 1 - otherwise
+batslib_is_caller() {
+ local -i is_mode_direct=1
+
+ # Handle options.
+ while (( $# > 0 )); do
+ case "$1" in
+ -i|--indirect) is_mode_direct=0; shift ;;
+ --) shift; break ;;
+ *) break ;;
+ esac
+ done
+
+ # Arguments.
+ local -r func="$1"
+
+ # Check call stack.
+ if (( is_mode_direct )); then
+ [[ $func == "${FUNCNAME[2]}" ]] && return 0
+ else
+ local -i depth
+ for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
+ [[ $func == "${FUNCNAME[$depth]}" ]] && return 0
+ done
+ fi
+
+ return 1
+}
diff --git a/test/52-lang-10-batslib_is_caller.bats b/test/52-lang-10-batslib_is_caller.bats
new file mode 100755
index 0000000..68fd59b
--- /dev/null
+++ b/test/52-lang-10-batslib_is_caller.bats
@@ -0,0 +1,88 @@
+#!/usr/bin/env bats
+
+load 'test_helper'
+
+
+# Test functions
+test_func_lvl_2() {
+ test_func_lvl_1 "$@"
+}
+
+test_func_lvl_1() {
+ test_func_lvl_0 "$@"
+}
+
+test_func_lvl_0() {
+ batslib_is_caller "$@"
+}
+
+
+#
+# Direct invocation
+#
+
+# Interface
+@test 'batslib_is_caller() <function>: returns 0 if the current function was called directly from <function>' {
+ run test_func_lvl_1 test_func_lvl_1
+ [ "$status" -eq 0 ]
+ [ "${#lines[@]}" -eq 0 ]
+}
+
+@test 'batslib_is_caller() <function>: returns 1 if the current function was not called directly from <function>' {
+ run test_func_lvl_0 test_func_lvl_1
+ [ "$status" -eq 1 ]
+ [ "${#lines[@]}" -eq 0 ]
+}
+
+# Correctness
+@test 'batslib_is_caller() <function>: the current function does not appear on the call stack' {
+ run test_func_lvl_0 test_func_lvl_0
+ [ "$status" -eq 1 ]
+ [ "${#lines[@]}" -eq 0 ]
+}
+
+
+#
+# Indirect invocation
+#
+
+# Options
+test_i_indirect() {
+ run test_func_lvl_2 "$@"
+ [ "$status" -eq 0 ]
+ [ "${#lines[@]}" -eq 0 ]
+}
+
+@test 'batslib_is_caller() -i <function>: enables indirect checking' {
+ test_i_indirect -i test_func_lvl_2
+}
+
+@test 'batslib_is_caller() --indirect <function>: enables indirect checking' {
+ test_i_indirect --indirect test_func_lvl_2
+}
+
+# Interface
+@test 'batslib_is_caller() --indirect <function>: returns 0 if the current function was called indirectly from <function>' {
+ run test_func_lvl_2 --indirect test_func_lvl_2
+ [ "$status" -eq 0 ]
+ [ "${#lines[@]}" -eq 0 ]
+}
+
+@test 'batslib_is_caller() --indirect <function>: returns 1 if the current function was not called indirectly from <function>' {
+ run test_func_lvl_1 --indirect test_func_lvl_2
+ [ "$status" -eq 1 ]
+ [ "${#lines[@]}" -eq 0 ]
+}
+
+# Correctness
+@test 'batslib_is_caller() --indirect <function>: direct invocation is a special case of indirect invocation with zero intermediate calls' {
+ run test_func_lvl_1 --indirect test_func_lvl_1
+ [ "$status" -eq 0 ]
+ [ "${#lines[@]}" -eq 0 ]
+}
+
+@test 'batslib_is_caller() --indirect <function>: the current function does not appear on the call stack' {
+ run test_func_lvl_0 --indirect test_func_lvl_0
+ [ "$status" -eq 1 ]
+ [ "${#lines[@]}" -eq 0 ]
+}