Skip to content

Commit 3b571e3

Browse files
committed
chore: main::exec_benchmarks
1 parent 95c421c commit 3b571e3

File tree

5 files changed

+188
-3
lines changed

5 files changed

+188
-3
lines changed

bashunit

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,22 @@ source "$BASHUNIT_ROOT_DIR/src/upgrade.sh"
2727
source "$BASHUNIT_ROOT_DIR/src/assertions.sh"
2828
source "$BASHUNIT_ROOT_DIR/src/reports.sh"
2929
source "$BASHUNIT_ROOT_DIR/src/runner.sh"
30+
source "$BASHUNIT_ROOT_DIR/src/benchmark.sh"
3031
source "$BASHUNIT_ROOT_DIR/src/bashunit.sh"
3132
source "$BASHUNIT_ROOT_DIR/src/main.sh"
3233

3334
_ASSERT_FN=""
3435
_FILTER=""
3536
_ARGS=()
37+
_BENCH_MODE=false
38+
39+
# Determine bench mode early so path arguments use correct pattern
40+
for arg in "$@"; do
41+
if [[ $arg == "-b" || $arg == "--bench" ]]; then
42+
_BENCH_MODE=true
43+
break
44+
fi
45+
done
3646

3747
check_os::init
3848
clock::init
@@ -61,6 +71,9 @@ while [[ $# -gt 0 ]]; do
6171
fi
6272
set -x
6373
;;
74+
-b|--bench)
75+
_BENCH_MODE=true
76+
;;
6477
-S|--stop-on-failure)
6578
export BASHUNIT_STOP_ON_FAILURE=true
6679
;;
@@ -99,9 +112,13 @@ while [[ $# -gt 0 ]]; do
99112
trap '' EXIT && exit 0
100113
;;
101114
*)
115+
pattern='*[tT]est.sh'
116+
if [[ "$_BENCH_MODE" == true ]]; then
117+
pattern='*[bB]ench.sh'
118+
fi
102119
while IFS='' read -r line; do
103120
_ARGS+=("$line");
104-
done < <(helper::find_files_recursive "$argument")
121+
done < <(helper::find_files_recursive "$argument" "$pattern")
105122
;;
106123
esac
107124
shift
@@ -114,6 +131,8 @@ set +eu
114131

115132
if [[ -n "$_ASSERT_FN" ]]; then
116133
main::exec_assert "$_ASSERT_FN" "${_ARGS[@]}"
134+
elif [[ "$_BENCH_MODE" == true ]]; then
135+
main::exec_benchmarks "$_FILTER" "${_ARGS[@]}"
117136
else
118137
main::exec_tests "$_FILTER" "${_ARGS[@]}"
119138
fi

src/benchmark.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env bash
2+
3+
_BENCH_NAMES=()
4+
_BENCH_REVS=()
5+
_BENCH_ITS=()
6+
_BENCH_AVERAGES=()
7+
8+
function benchmark::parse_annotations() {
9+
local fn_name=$1
10+
local script=$2
11+
local revs=1
12+
local its=1
13+
14+
local annotation
15+
annotation=$(awk "/function[[:space:]]+$fn_name[[:space:]]*\(/ {print prev; exit} {prev=\$0}" "$script")
16+
17+
if [[ $annotation =~ @revs=([0-9]+) ]]; then
18+
revs="${BASH_REMATCH[1]}"
19+
elif [[ $annotation =~ @revolutions=([0-9]+) ]]; then
20+
revs="${BASH_REMATCH[1]}"
21+
fi
22+
23+
if [[ $annotation =~ @its=([0-9]+) ]]; then
24+
its="${BASH_REMATCH[1]}"
25+
elif [[ $annotation =~ @iterations=([0-9]+) ]]; then
26+
its="${BASH_REMATCH[1]}"
27+
fi
28+
29+
echo "$revs" "$its"
30+
}
31+
32+
function benchmark::add_result() {
33+
_BENCH_NAMES+=("$1")
34+
_BENCH_REVS+=("$2")
35+
_BENCH_ITS+=("$3")
36+
_BENCH_AVERAGES+=("$4")
37+
}
38+
39+
function benchmark::run_function() {
40+
local fn_name=$1
41+
local revs=$2
42+
local its=$3
43+
local durations=()
44+
45+
for ((i=1; i<=its; i++)); do
46+
local start_time=$(clock::now)
47+
(
48+
for ((r=1; r<=revs; r++)); do
49+
"$fn_name" >/dev/null 2>&1
50+
done
51+
)
52+
local end_time=$(clock::now)
53+
local dur_ns=$(math::calculate "($end_time - $start_time)")
54+
local dur_ms=$(math::calculate "$dur_ns / 1000000")
55+
durations+=("$dur_ms")
56+
57+
local line="bench $fn_name [$i/$its] ${dur_ms} ms"
58+
state::print_line "successful" "$line"
59+
done
60+
61+
local sum=0
62+
for d in "${durations[@]}"; do
63+
sum=$(math::calculate "$sum + $d")
64+
done
65+
local avg=$(math::calculate "$sum / ${#durations[@]}")
66+
benchmark::add_result "$fn_name" "$revs" "$its" "$avg"
67+
}
68+
69+
function benchmark::print_results() {
70+
if (( ${#_BENCH_NAMES[@]} == 0 )); then
71+
return
72+
fi
73+
if env::is_simple_output_enabled; then
74+
printf "\n"
75+
fi
76+
printf "\nBenchmark Results (avg ms)\n"
77+
printf '%-40s %8s %8s %8s\n' "Name" "Revs" "Its" "Avg(ms)"
78+
for i in "${!_BENCH_NAMES[@]}"; do
79+
printf '%-40s %8s %8s %8s\n' "${_BENCH_NAMES[$i]}" "${_BENCH_REVS[$i]}" "${_BENCH_ITS[$i]}" "${_BENCH_AVERAGES[$i]}"
80+
done
81+
}

src/helpers.sh

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,12 @@ function helper::unset_if_exists() {
125125
function helper::find_files_recursive() {
126126
## Remove trailing slash using parameter expansion
127127
local path="${1%%/}"
128+
local pattern="${2:-*[tT]est.sh}"
128129

129130
if [[ "$path" == *"*"* ]]; then
130-
eval find "$path" -type f -name '*[tT]est.sh' | sort -u
131+
eval find "$path" -type f -name "$pattern" | sort -u
131132
elif [[ -d "$path" ]]; then
132-
find "$path" -type f -name '*[tT]est.sh' | sort -u
133+
find "$path" -type f -name "$pattern" | sort -u
133134
else
134135
echo "$path"
135136
fi
@@ -259,3 +260,22 @@ function helper::load_test_files() {
259260

260261
printf "%s\n" "${test_files[@]}"
261262
}
263+
264+
function helper::load_bench_files() {
265+
local filter=$1
266+
local files=("${@:2}")
267+
268+
local bench_files=()
269+
270+
if [[ "${#files[@]}" -eq 0 ]]; then
271+
if [[ -n "${BASHUNIT_DEFAULT_PATH}" ]]; then
272+
while IFS='' read -r line; do
273+
bench_files+=("$line")
274+
done < <(helper::find_files_recursive "$BASHUNIT_DEFAULT_PATH" '*bench.sh')
275+
fi
276+
else
277+
bench_files=("${files[@]}")
278+
fi
279+
280+
printf "%s\n" "${bench_files[@]}"
281+
}

src/main.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ function main::exec_tests() {
7272
exit $exit_code
7373
}
7474

75+
function main::exec_benchmarks() {
76+
local filter=$1
77+
local files=("${@:2}")
78+
79+
local bench_files=()
80+
while IFS= read -r line; do
81+
bench_files+=("$line")
82+
done < <(helper::load_bench_files "$filter" "${files[@]}")
83+
84+
if [[ ${#bench_files[@]} -eq 0 || -z "${bench_files[0]}" ]]; then
85+
printf "%sError: At least one file path is required.%s\n" "${_COLOR_FAILED}" "${_COLOR_DEFAULT}"
86+
console_header::print_help
87+
exit 1
88+
fi
89+
90+
console_header::print_version_with_env "$filter" "${bench_files[@]}"
91+
92+
runner::load_bench_files "$filter" "${bench_files[@]}"
93+
94+
benchmark::print_results
95+
96+
cleanup_temp_files
97+
}
98+
7599
function main::cleanup() {
76100
printf "%sCaught Ctrl-C, killing all child processes...%s\n" "${_COLOR_SKIPPED}" "${_COLOR_DEFAULT}"
77101
# Kill all child processes of this script

src/runner.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ function runner::load_test_files() {
3333
fi
3434
}
3535

36+
function runner::load_bench_files() {
37+
local filter=$1
38+
shift
39+
local files=("${@}")
40+
41+
for bench_file in "${files[@]}"; do
42+
[[ -f $bench_file ]] || continue
43+
# shellcheck source=/dev/null
44+
source "$bench_file"
45+
runner::run_set_up_before_script
46+
runner::call_bench_functions "$bench_file" "$filter"
47+
runner::run_tear_down_after_script
48+
runner::clean_set_up_and_tear_down_after_script
49+
done
50+
}
51+
3652
function runner::spinner() {
3753
if env::is_simple_output_enabled; then
3854
printf "\n"
@@ -113,6 +129,31 @@ function runner::call_test_functions() {
113129
fi
114130
}
115131

132+
function runner::call_bench_functions() {
133+
local script="$1"
134+
local filter="$2"
135+
local prefix="bench"
136+
137+
local all_fn_names=$(declare -F | awk '{print $3}')
138+
local filtered_functions=$(helper::get_functions_to_run "$prefix" "$filter" "$all_fn_names")
139+
# shellcheck disable=SC2207
140+
local functions_to_run=($(runner::functions_for_script "$script" "$filtered_functions"))
141+
142+
if [[ "${#functions_to_run[@]}" -le 0 ]]; then
143+
return
144+
fi
145+
146+
for fn_name in "${functions_to_run[@]}"; do
147+
read -r revs its <<< "$(benchmark::parse_annotations "$fn_name" "$script")"
148+
benchmark::run_function "$fn_name" "$revs" "$its"
149+
unset fn_name
150+
done
151+
152+
if ! env::is_simple_output_enabled; then
153+
echo ""
154+
fi
155+
}
156+
116157
function runner::render_running_file_header() {
117158
if parallel::is_enabled; then
118159
return

0 commit comments

Comments
 (0)