Skip to content

Commit f9511d3

Browse files
committed
lib/test/stack: Use lib/bats/helper-function
Per the deleted comment from `@go.stack_trace_item`, I've figured out why it was faster to run `for` and `while` loops as a new process. Since Bats registers the `bats_debug_trap` function to collect stack trace information, and `DEBUG` traps fire before every iteration of a loop, that cost slowed down the implementation when it was originally in-process. Now that the `set "$DISABLE_BATS_SHELL_OPTIONS"` and `restore_bats_shell_options` mechanism disables `DEBUG` traps from being inherited by invoked functions, we can add `__go.stack_trace_item` back to the `lib/testing/stack-trace` file and remove the external `lib/testing/stack-trace-item` script. This commit applies the mechanism to other public API functions as well. This also results in a speed increase on my MacBook Pro for the following command: ./go test file/open-or-dup log/add-output-file log/add-update \ log/filters log/log-command log/main log/setup-project modules/use \ testing/stack-trace from this: 119 tests, 0 failures real 0m37.419s user 0m18.710s sys 0m16.910s to this: 119 tests, 0 failures real 0m31.346s user 0m16.186s sys 0m13.627s The speedups from #159 and #160 already pulled test times on Windows from the high 50-minute range down to the mid 30-minute range, so I look forward to the impact this change will have on that platform.
1 parent fd99df5 commit f9511d3

File tree

2 files changed

+154
-135
lines changed

2 files changed

+154
-135
lines changed

lib/testing/stack-trace

Lines changed: 154 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#
55
# You must source `_GO_CORE_DIR/lib/testing/environment` before this file.
66

7+
. "$_GO_CORE_DIR/lib/bats/helper-function"
8+
79
# Get the stack trace of a line from a file or function as it would appear in
810
# `@go.print_stack_trace` output.
911
#
@@ -12,25 +14,10 @@
1214
# function_name: Function in which the line appears, 'main', or 'source'
1315
# target_line: Line for which to produce a stack trace line
1416
@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 "$?"
3421
}
3522

3623
# Creates `GO_CORE_STACK_TRACE_COMPONENTS` to help validate stack trace output.
@@ -49,27 +36,9 @@
4936
# Stack trace lines from `go-core.bash` comprising the command script
5037
# dispatch mechanism
5138
@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 "$?"
7342
}
7443

7544
# Creates a stack trace line using an offset from the last line of `filename`.
@@ -82,6 +51,7 @@
8251
# offset: Offset from the last line to get the line number (default 0)
8352
# funcname: Name of the function expected in the stack trace (default 'main')
8453
@go.stack_trace_item_from_offset() {
54+
set "$DISABLE_BATS_SHELL_OPTIONS"
8555
local filepath="${1}"
8656
local offset="${2:-0}"
8757
local funcname="${3:-main}"
@@ -92,6 +62,7 @@
9262
@go.count_lines "$filepath" 'num_lines'
9363
lineno="$((num_lines - offset))"
9464
printf ' %s:%d %s\n' "$filepath" "$lineno" "$funcname"
65+
restore_bats_shell_options "$?"
9566
}
9667

9768
# Counts the number of lines in a file.
@@ -100,14 +71,155 @@
10071
# filepath: Path to the file
10172
# result_name: Name of the caller's variable in which to store the result
10273
@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() {
103216
local filepath="$1"
104217
local result_name="$2"
105218
local line
106219
local i='0'
107220

108-
__@go.check_file_path_specified_and_present "$filepath"
109221
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
111223
exit 1
112224
fi
113225

@@ -117,12 +229,6 @@
117229
printf -v "$result_name" '%d' "$i"
118230
}
119231

120-
# --------------------------------
121-
# IMPLEMENTATION - HERE BE DRAGONS
122-
#
123-
# None of the functions below this line are part of the public interface.
124-
# --------------------------------
125-
126232
# Confirms that a file name is specified and the file exists.
127233
#
128234
# Will print a message to standard error and exit on error.

lib/testing/stack-trace-item

Lines changed: 0 additions & 87 deletions
This file was deleted.

0 commit comments

Comments
 (0)