|
4 | 4 | # |
5 | 5 | # You must source `_GO_CORE_DIR/lib/testing/environment` before this file. |
6 | 6 |
|
| 7 | +. "$_GO_CORE_DIR/lib/bats/helper-function" |
| 8 | + |
7 | 9 | # Get the stack trace of a line from a file or function as it would appear in |
8 | 10 | # `@go.print_stack_trace` output. |
9 | 11 | # |
|
12 | 14 | # function_name: Function in which the line appears, 'main', or 'source' |
13 | 15 | # target_line: Line for which to produce a stack trace line |
14 | 16 | @go.stack_trace_item() { |
15 | | - local filepath="$1" |
16 | | - local function_name="$2" |
17 | | - local target_line="$3" |
18 | | - |
19 | | - __@go.check_file_path_specified_and_present "$filepath" |
20 | | - |
21 | | - if [[ -z "$function_name" ]]; then |
22 | | - printf 'No function name specified for `%s`.\n' "$FUNCNAME" >&2 |
23 | | - exit 1 |
24 | | - elif [[ "$function_name" =~ ^(main|source)$ && -z "$target_line" ]]; then |
25 | | - printf 'No target line from `%s` specified for `%s`.\n' \ |
26 | | - "$function_name" "$FUNCNAME" >&2 |
27 | | - exit 1 |
28 | | - fi |
29 | | - |
30 | | - # Seriously, it's faster to run a script containing a `for` or `while read` |
31 | | - # loop over a file as a new process than it is to run the function in-process |
32 | | - # under Bats. Haven't yet figured out why. |
33 | | - "${BASH_SOURCE%/*}/stack-trace-item" "$@" |
| 17 | + set "$DISABLE_BATS_SHELL_OPTIONS" |
| 18 | + __@go.check_file_path_specified_and_present "$1" |
| 19 | + __@go.stack_trace_item "$@" |
| 20 | + restore_bats_shell_options "$?" |
34 | 21 | } |
35 | 22 |
|
36 | 23 | # Creates `GO_CORE_STACK_TRACE_COMPONENTS` to help validate stack trace output. |
|
49 | 36 | # Stack trace lines from `go-core.bash` comprising the command script |
50 | 37 | # dispatch mechanism |
51 | 38 | @go.set_go_core_stack_trace_components() { |
52 | | - local go_core_file="$_GO_CORE_DIR/go-core.bash" |
53 | | - local stack_item |
54 | | - local script="$TEST_GO_ROOTDIR/generate-go-core-stack-trace" |
55 | | - local IFS=$'\n' |
56 | | - |
57 | | - if [[ "${#GO_CORE_STACK_TRACE_COMPONENTS[@]}" -eq '0' ]]; then |
58 | | - create_bats_test_script "${script#$TEST_GO_ROOTDIR}" \ |
59 | | - ". '$_GO_CORE_DIR/go-core.bash' '$TEST_GO_SCRIPTS_RELATIVE_DIR'" \ |
60 | | - '@go "$@"' |
61 | | - @go.create_test_command_script 'print-stack-trace' '@go.print_stack_trace' |
62 | | - |
63 | | - for stack_item in $("$script" 'print-stack-trace'); do |
64 | | - if [[ "$stack_item" =~ $go_core_file ]]; then |
65 | | - GO_CORE_STACK_TRACE_COMPONENTS+=("$stack_item") |
66 | | - elif [[ "${#_GO_CORE_STACK_TRACE_COMPONENTS[@]}" -ne '0' ]]; then |
67 | | - return |
68 | | - fi |
69 | | - done |
70 | | - rm "$script" "$TEST_GO_SCRIPTS_DIR/print-stack-trace" |
71 | | - export GO_CORE_STACK_TRACE_COMPONENTS |
72 | | - fi |
| 39 | + set "$DISABLE_BATS_SHELL_OPTIONS" |
| 40 | + __@go.set_go_core_stack_trace_components |
| 41 | + restore_bats_shell_options "$?" |
73 | 42 | } |
74 | 43 |
|
75 | 44 | # Creates a stack trace line using an offset from the last line of `filename`. |
|
82 | 51 | # offset: Offset from the last line to get the line number (default 0) |
83 | 52 | # funcname: Name of the function expected in the stack trace (default 'main') |
84 | 53 | @go.stack_trace_item_from_offset() { |
| 54 | + set "$DISABLE_BATS_SHELL_OPTIONS" |
85 | 55 | local filepath="${1}" |
86 | 56 | local offset="${2:-0}" |
87 | 57 | local funcname="${3:-main}" |
|
92 | 62 | @go.count_lines "$filepath" 'num_lines' |
93 | 63 | lineno="$((num_lines - offset))" |
94 | 64 | printf ' %s:%d %s\n' "$filepath" "$lineno" "$funcname" |
| 65 | + restore_bats_shell_options "$?" |
95 | 66 | } |
96 | 67 |
|
97 | 68 | # Counts the number of lines in a file. |
|
100 | 71 | # filepath: Path to the file |
101 | 72 | # result_name: Name of the caller's variable in which to store the result |
102 | 73 | @go.count_lines() { |
| 74 | + set "$DISABLE_BATS_SHELL_OPTIONS" |
| 75 | + __@go.check_file_path_specified_and_present "$1" |
| 76 | + __@go.count_lines "$@" |
| 77 | + restore_bats_shell_options "$?" |
| 78 | +} |
| 79 | + |
| 80 | +# -------------------------------- |
| 81 | +# IMPLEMENTATION - HERE BE DRAGONS |
| 82 | +# |
| 83 | +# None of the functions below this line are part of the public interface. |
| 84 | +# -------------------------------- |
| 85 | + |
| 86 | +# Implementation for `@go.stack_trace_item`, extracted for efficiency |
| 87 | +# |
| 88 | +# Arguments: |
| 89 | +# filepath: Path to the file containing the line |
| 90 | +# function_name: Function in which the line appears, 'main', or 'source' |
| 91 | +# target_line: Line for which to produce a stack trace line |
| 92 | +__@go.stack_trace_item() { |
| 93 | + local filepath="$1" |
| 94 | + local function_name="$2" |
| 95 | + local target_line="$3" |
| 96 | + local initial_state='FUNCTION' |
| 97 | + local state |
| 98 | + local lineno=0 |
| 99 | + local line |
| 100 | + |
| 101 | + # Granted, this will match an illegal line like 'foo {' that has neither |
| 102 | + # 'function' nor '()', but it's probably not worth splitting hairs here. |
| 103 | + local function_opening='^(function )? *([[:alnum:]_.@-]+) *(\(\))? *\{' |
| 104 | + |
| 105 | + if [[ -z "$function_name" ]]; then |
| 106 | + printf 'No function name specified for `%s`.\n' "${FUNCNAME[1]}" >&2 |
| 107 | + exit 1 |
| 108 | + elif [[ "$function_name" =~ ^(main|source)$ && -z "$target_line" ]]; then |
| 109 | + printf 'No target line from `%s` specified for `%s`.\n' \ |
| 110 | + "$function_name" "${FUNCNAME[1]}" >&2 |
| 111 | + exit 1 |
| 112 | + fi |
| 113 | + |
| 114 | + if [[ "$function_name" == 'main' || "$function_name" == 'source' ]]; then |
| 115 | + initial_state='MAIN' |
| 116 | + fi |
| 117 | + state="$initial_state" |
| 118 | + |
| 119 | + # We have to set `IFS=` to preserve leading spaces, and then for Windows |
| 120 | + # compatibility, strip off the trailing carriage return (the newline is |
| 121 | + # already chomped off). |
| 122 | + while IFS= read -r line; do |
| 123 | + line="${line%$'\r'}" |
| 124 | + ((++lineno)) |
| 125 | + |
| 126 | + case "$state" in |
| 127 | + MAIN) |
| 128 | + if [[ "$line" == "$target_line" ]]; then |
| 129 | + state='SUCCESS' |
| 130 | + break |
| 131 | + elif [[ "$line" =~ $function_opening ]]; then |
| 132 | + state='SKIP_FUNCTION' |
| 133 | + fi |
| 134 | + ;; |
| 135 | + FUNCTION) |
| 136 | + if [[ "$line" =~ $function_opening ]]; then |
| 137 | + if [[ "${BASH_REMATCH[2]}" != "$function_name" ]]; then |
| 138 | + state='SKIP_FUNCTION' |
| 139 | + elif [[ -z "$target_line" ]]; then |
| 140 | + state='SUCCESS' |
| 141 | + break |
| 142 | + else |
| 143 | + state='INSIDE_TARGET_FUNCTION' |
| 144 | + fi |
| 145 | + fi |
| 146 | + ;; |
| 147 | + INSIDE_TARGET_FUNCTION) |
| 148 | + if [[ "$line" == "$target_line" ]]; then |
| 149 | + state='SUCCESS' |
| 150 | + break |
| 151 | + elif [[ "$line" == '}' ]]; then |
| 152 | + state='FOUND_FUNCTION' |
| 153 | + break |
| 154 | + fi |
| 155 | + ;; |
| 156 | + SKIP_FUNCTION) |
| 157 | + if [[ "$line" == '}' ]]; then |
| 158 | + state="$initial_state" |
| 159 | + fi |
| 160 | + ;; |
| 161 | + esac |
| 162 | + done <"$filepath" |
| 163 | + |
| 164 | + case "$state" in |
| 165 | + SUCCESS) |
| 166 | + printf ' %s:%d %s\n' "$filepath" "$lineno" "$function_name" |
| 167 | + return |
| 168 | + ;; |
| 169 | + MAIN|FOUND_FUNCTION) |
| 170 | + printf 'Line not found in `%s` from "%s": "%s"\n' \ |
| 171 | + "$function_name" "$filepath" "$target_line" >&2 |
| 172 | + ;; |
| 173 | + *) |
| 174 | + printf 'Function `%s` not found in "%s".\n' "$function_name" "$filepath" >&2 |
| 175 | + ;; |
| 176 | + esac |
| 177 | + return '1' |
| 178 | +} |
| 179 | + |
| 180 | +# Implementation for `@go.set_go_core_stack_trace_components` |
| 181 | +# |
| 182 | +# Globals: |
| 183 | +# GO_CORE_STACK_TRACE_COMPONENTS: |
| 184 | +# Stack trace lines from `go-core.bash` comprising the command script |
| 185 | +# dispatch mechanism |
| 186 | +__@go.set_go_core_stack_trace_components() { |
| 187 | + local go_core_file="$_GO_CORE_DIR/go-core.bash" |
| 188 | + local stack_item |
| 189 | + local script="$TEST_GO_ROOTDIR/generate-go-core-stack-trace" |
| 190 | + local IFS=$'\n' |
| 191 | + |
| 192 | + if [[ "${#GO_CORE_STACK_TRACE_COMPONENTS[@]}" -eq '0' ]]; then |
| 193 | + create_bats_test_script "${script#$TEST_GO_ROOTDIR}" \ |
| 194 | + ". '$_GO_CORE_DIR/go-core.bash' '$TEST_GO_SCRIPTS_RELATIVE_DIR'" \ |
| 195 | + '@go "$@"' |
| 196 | + @go.create_test_command_script 'print-stack-trace' '@go.print_stack_trace' |
| 197 | + |
| 198 | + for stack_item in $("$script" 'print-stack-trace'); do |
| 199 | + if [[ "$stack_item" =~ $go_core_file ]]; then |
| 200 | + GO_CORE_STACK_TRACE_COMPONENTS+=("$stack_item") |
| 201 | + elif [[ "${#_GO_CORE_STACK_TRACE_COMPONENTS[@]}" -ne '0' ]]; then |
| 202 | + return |
| 203 | + fi |
| 204 | + done |
| 205 | + rm "$script" "$TEST_GO_SCRIPTS_DIR/print-stack-trace" |
| 206 | + export GO_CORE_STACK_TRACE_COMPONENTS |
| 207 | + fi |
| 208 | +} |
| 209 | + |
| 210 | +# Implementation for @go.count_lines, extracted for efficiency |
| 211 | +# |
| 212 | +# Arguments: |
| 213 | +# filepath: Path to the file |
| 214 | +# result_name: Name of the caller's variable in which to store the result |
| 215 | +__@go.count_lines() { |
103 | 216 | local filepath="$1" |
104 | 217 | local result_name="$2" |
105 | 218 | local line |
106 | 219 | local i='0' |
107 | 220 |
|
108 | | - __@go.check_file_path_specified_and_present "$filepath" |
109 | 221 | if [[ -z "$result_name" ]]; then |
110 | | - printf 'No result variable specified for `%s`.\n' "$FUNCNAME" >&2 |
| 222 | + printf 'No result variable specified for `%s`.\n' "${FUNCNAME[1]}" >&2 |
111 | 223 | exit 1 |
112 | 224 | fi |
113 | 225 |
|
|
117 | 229 | printf -v "$result_name" '%d' "$i" |
118 | 230 | } |
119 | 231 |
|
120 | | -# -------------------------------- |
121 | | -# IMPLEMENTATION - HERE BE DRAGONS |
122 | | -# |
123 | | -# None of the functions below this line are part of the public interface. |
124 | | -# -------------------------------- |
125 | | - |
126 | 232 | # Confirms that a file name is specified and the file exists. |
127 | 233 | # |
128 | 234 | # Will print a message to standard error and exit on error. |
|
0 commit comments