Skip to content

Commit 76c6629

Browse files
authored
Merge pull request #424 from TypedDevs/chore/max-ms-on-bench
Add max_ms option on bench
2 parents 4b9f62f + 6eaf9f0 commit 76c6629

File tree

10 files changed

+151
-19
lines changed

10 files changed

+151
-19
lines changed

bashunit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ _BENCH_MODE=false
3939
# Determine bench mode early so path arguments use correct pattern
4040
for arg in "$@"; do
4141
if [[ $arg == "-b" || $arg == "--bench" ]]; then
42+
export BASHUNIT_BENCH_MODE=true
4243
_BENCH_MODE=true
4344
break
4445
fi

docs/benchmarks.md

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ bashunit allows defining benchmark functions to measure execution time of your s
44

55
Functions prefixed with `bench` are treated as benchmarks. You can annotate them with
66
`@revs` (revolutions) and `@its` (iterations) comments to control how many times the code
7-
is executed.
7+
is executed. Use `@max_ms` to mark the benchmark as failed when the average
8+
execution time exceeds the given value in milliseconds.
89

910
Each iteration is executed in a separate process, and the average time is reported.
1011

@@ -15,6 +16,15 @@ function bench_my_function() {
1516
}
1617
```
1718

19+
You can optionally fail a benchmark when it exceeds a max_ms:
20+
21+
```bash
22+
# @revs=10 @its=1 @max_ms=5
23+
function bench_slow() {
24+
slow_operation
25+
}
26+
```
27+
1828
Run benchmarks using the `--bench` option. Each iteration prints its duration
1929
when using detailed output, or a dot when simple output is enabled:
2030

@@ -26,13 +36,57 @@ If no file is provided, **bashunit** uses `BASHUNIT_DEFAULT_PATH` to locate all
2636

2737
Example output:
2838

29-
```-vue
30-
bashunit - {{ pkg.version }}
39+
::: code-group
40+
```bash [Simple]
41+
./bashunit --bench --simple
42+
43+
.........
44+
45+
Benchmark Results (avg ms)
46+
======================================================================
47+
48+
Name Revs Its Avg(ms) Status
49+
bench_bashunit_runs_benchmarks 3 2 727
50+
bench_bashunit_functional_run 1 1 243
51+
bench_bashunit_default_path 1 1 731 > 600
52+
bench_run_bashunit_functional 2 1 371 > 300
53+
bench_sleep 5 2 21 ≤ 25
54+
bench_sleep_synonym 3 2 32
55+
```
56+
```bash [Detailed]
57+
./bashunit --bench
58+
59+
Running tests/benchmark/bashunit_bench.sh
60+
Bench bashunit runs benchmarks [1/2] 843 ms
61+
Bench bashunit runs benchmarks [2/2] 834 ms
62+
Bench bashunit functional run [1/1] 281 ms
63+
Bench bashunit default path [1/1] 736 ms
64+
65+
Running tests/benchmark/fixtures/bashunit_functional_bench.sh
66+
Bench run bashunit functional [1/1] 430 ms
67+
68+
Running tests/benchmark/fixtures/bashunit_sleep_bench.sh
69+
Bench sleep [1/2] 48 ms
70+
Bench sleep [2/2] 26 ms
71+
Bench sleep synonym [1/2] 32 ms
72+
Bench sleep synonym [2/2] 34 ms
73+
3174

3275
Benchmark Results (avg ms)
33-
Name Revs Its Avg(ms)
34-
bench_my_function 1000 5 12.34
76+
=====================================================================
77+
78+
Name Revs Its Avg(ms) Status
79+
bench_bashunit_runs_benchmarks 3 2 838
80+
bench_bashunit_functional_run 1 1 281
81+
bench_bashunit_default_path 1 1 736 > 600
82+
bench_run_bashunit_functional 2 1 430 > 300
83+
bench_sleep 5 2 37 > 25
84+
bench_sleep_synonym 3 2 33
85+
3586
```
87+
:::
88+
89+
3690

3791
<script setup>
3892
import pkg from '../package.json'

src/benchmark.sh

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ _BENCH_NAMES=()
44
_BENCH_REVS=()
55
_BENCH_ITS=()
66
_BENCH_AVERAGES=()
7+
_BENCH_MAX_MILLIS=()
78

89
function benchmark::parse_annotations() {
910
local fn_name=$1
1011
local script=$2
1112
local revs=1
1213
local its=1
14+
local max_ms=""
1315

1416
local annotation
1517
annotation=$(awk "/function[[:space:]]+${fn_name}[[:space:]]*\(/ {print prev; exit} {prev=\$0}" "$script")
@@ -26,21 +28,33 @@ function benchmark::parse_annotations() {
2628
its="${BASH_REMATCH[1]}"
2729
fi
2830

29-
echo "$revs" "$its"
31+
if [[ $annotation =~ @max_ms=([0-9.]+) ]]; then
32+
max_ms="${BASH_REMATCH[1]}"
33+
elif [[ $annotation =~ @max_ms=([0-9.]+) ]]; then
34+
max_ms="${BASH_REMATCH[1]}"
35+
fi
36+
37+
if [[ -n "$max_ms" ]]; then
38+
echo "$revs" "$its" "$max_ms"
39+
else
40+
echo "$revs" "$its"
41+
fi
3042
}
3143

3244
function benchmark::add_result() {
3345
_BENCH_NAMES+=("$1")
3446
_BENCH_REVS+=("$2")
3547
_BENCH_ITS+=("$3")
3648
_BENCH_AVERAGES+=("$4")
49+
_BENCH_MAX_MILLIS+=("$5")
3750
}
3851

3952
# shellcheck disable=SC2155
4053
function benchmark::run_function() {
4154
local fn_name=$1
4255
local revs=$2
4356
local its=$3
57+
local max_ms=$4
4458
local durations=()
4559

4660
for ((i=1; i<=its; i++)); do
@@ -55,28 +69,75 @@ function benchmark::run_function() {
5569
local dur_ms=$(math::calculate "$dur_ns / 1000000")
5670
durations+=("$dur_ms")
5771

58-
local line="bench $fn_name [$i/$its] ${dur_ms} ms"
59-
state::print_line "successful" "$line"
72+
if env::is_bench_mode_enabled; then
73+
local label="$(helper::normalize_test_function_name "$fn_name")"
74+
local line="$label [$i/$its] ${dur_ms} ms"
75+
state::print_line "successful" "$line"
76+
fi
6077
done
6178

6279
local sum=0
6380
for d in "${durations[@]}"; do
6481
sum=$(math::calculate "$sum + $d")
6582
done
6683
local avg=$(math::calculate "$sum / ${#durations[@]}")
67-
benchmark::add_result "$fn_name" "$revs" "$its" "$avg"
84+
benchmark::add_result "$fn_name" "$revs" "$its" "$avg" "$max_ms"
6885
}
6986

7087
function benchmark::print_results() {
88+
if ! env::is_bench_mode_enabled; then
89+
return
90+
fi
91+
7192
if (( ${#_BENCH_NAMES[@]} == 0 )); then
7293
return
7394
fi
95+
7496
if env::is_simple_output_enabled; then
7597
printf "\n"
7698
fi
99+
77100
printf "\nBenchmark Results (avg ms)\n"
78-
printf '%-40s %8s %8s %8s\n' "Name" "Revs" "Its" "Avg(ms)"
101+
print_line 80 "="
102+
printf "\n"
103+
104+
local has_threshold=false
105+
for val in "${_BENCH_MAX_MILLIS[@]}"; do
106+
if [[ -n "$val" ]]; then
107+
has_threshold=true
108+
break
109+
fi
110+
done
111+
112+
if $has_threshold; then
113+
printf '%-40s %6s %6s %10s %12s\n' "Name" "Revs" "Its" "Avg(ms)" "Status"
114+
else
115+
printf '%-40s %6s %6s %10s\n' "Name" "Revs" "Its" "Avg(ms)"
116+
fi
117+
79118
for i in "${!_BENCH_NAMES[@]}"; do
80-
printf '%-40s %8s %8s %8s\n' "${_BENCH_NAMES[$i]}" "${_BENCH_REVS[$i]}" "${_BENCH_ITS[$i]}" "${_BENCH_AVERAGES[$i]}"
119+
local name="${_BENCH_NAMES[$i]}"
120+
local revs="${_BENCH_REVS[$i]}"
121+
local its="${_BENCH_ITS[$i]}"
122+
local avg="${_BENCH_AVERAGES[$i]}"
123+
local max_ms="${_BENCH_MAX_MILLIS[$i]}"
124+
125+
if [[ -z "$max_ms" ]]; then
126+
printf '%-40s %6s %6s %10s\n' "$name" "$revs" "$its" "$avg"
127+
continue
128+
fi
129+
130+
if (( $(echo "$avg <= $max_ms" | bc -l) )); then
131+
local raw="${max_ms}"
132+
printf -v padded "%14s" "$raw"
133+
printf '%-40s %6s %6s %10s %12s\n' "$name" "$revs" "$its" "$avg" "$padded"
134+
continue
135+
fi
136+
137+
local raw="> ${max_ms}"
138+
printf -v padded "%12s" "$raw"
139+
printf '%-40s %6s %6s %10s %s%s%s\n' \
140+
"$name" "$revs" "$its" "$avg" \
141+
"$_COLOR_FAILED" "$padded" "${_COLOR_DEFAULT}"
81142
done
82143
}

src/env.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ _DEFAULT_SIMPLE_OUTPUT="false"
2727
_DEFAULT_STOP_ON_FAILURE="false"
2828
_DEFAULT_SHOW_EXECUTION_TIME="true"
2929
_DEFAULT_VERBOSE="false"
30+
_DEFAULT_BENCH_MODE="false"
3031

3132
: "${BASHUNIT_PARALLEL_RUN:=${PARALLEL_RUN:=$_DEFAULT_PARALLEL_RUN}}"
3233
: "${BASHUNIT_SHOW_HEADER:=${SHOW_HEADER:=$_DEFAULT_SHOW_HEADER}}"
@@ -35,6 +36,7 @@ _DEFAULT_VERBOSE="false"
3536
: "${BASHUNIT_STOP_ON_FAILURE:=${STOP_ON_FAILURE:=$_DEFAULT_STOP_ON_FAILURE}}"
3637
: "${BASHUNIT_SHOW_EXECUTION_TIME:=${SHOW_EXECUTION_TIME:=$_DEFAULT_SHOW_EXECUTION_TIME}}"
3738
: "${BASHUNIT_VERBOSE:=${VERBOSE:=$_DEFAULT_VERBOSE}}"
39+
: "${BASHUNIT_BENCH_MODE:=${BENCH_MODE:=$_DEFAULT_BENCH_MODE}}"
3840

3941
function env::is_parallel_run_enabled() {
4042
[[ "$BASHUNIT_PARALLEL_RUN" == "true" ]]
@@ -68,6 +70,10 @@ function env::is_verbose_enabled() {
6870
[[ "$BASHUNIT_VERBOSE" == "true" ]]
6971
}
7072

73+
function env::is_bench_mode_enabled() {
74+
[[ "$BASHUNIT_BENCH_MODE" == "true" ]]
75+
}
76+
7177
function env::active_internet_connection() {
7278
if ping -c 1 -W 3 google.com &> /dev/null; then
7379
return 0

src/globals.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,9 @@ function log() {
8787
local RESET='\033[0m'
8888
echo -e "$(current_timestamp) [$level]: $@ ${GRAY}#${BASH_SOURCE[1]}:${BASH_LINENO[0]}${RESET}" >> "$BASHUNIT_DEV_LOG"
8989
}
90+
91+
function print_line() {
92+
local length="${1:-70}" # Default to 70 if not passed
93+
local char="${2:--}" # Default to '-' if not passed
94+
printf '%*s\n' "$length" '' | tr ' ' "$char"
95+
}

src/runner.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,13 @@ function runner::call_bench_functions() {
143143
return
144144
fi
145145

146+
if env::is_bench_mode_enabled; then
147+
runner::render_running_file_header "$script"
148+
fi
149+
146150
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"
151+
read -r revs its max_ms <<< "$(benchmark::parse_annotations "$fn_name" "$script")"
152+
benchmark::run_function "$fn_name" "$revs" "$its" "$max_ms"
149153
unset fn_name
150154
done
151155

tests/benchmark/bashunit_bench.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function bench_bashunit_functional_run() {
2121
assert_successful_code "$output"
2222
}
2323

24-
# @revs=1 @its=1
24+
# @revs=1 @its=1 @max_ms=600
2525
function bench_bashunit_default_path() {
2626
local env_file=./tests/benchmark/fixtures/.env.with_path
2727

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bash
22

3-
# @revs=2 @its=1
3+
# @revs=2 @its=1 @max_ms=300
44
function bench_run_bashunit_functional() {
55
./bashunit tests/functional/for_bench_test.sh -s -p >/dev/null
66
}

tests/benchmark/fixtures/bashunit_sleep_bench.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#!/usr/bin/env bash
22

3-
# @revs=5 @its=2
3+
# @revs=5 @its=2 @max_ms=25
44
function bench_sleep() {
5-
sleep 0.01
5+
sleep 0.001
66
}
77

88
# @revolutions=3 @iterations=2

tests/unit/benchmark_test.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ function set_up() {
55
}
66

77
function test_parse_annotations() {
8-
assert_same "5 2" "$(benchmark::parse_annotations bench_sleep "$SCRIPT")"
8+
assert_same "5 2 25" "$(benchmark::parse_annotations bench_sleep "$SCRIPT")"
99
}
1010

1111
function test_parse_annotations_with_synonyms() {
@@ -21,7 +21,7 @@ function test_run_function_collects_results() {
2121
_BENCH_ITS=()
2222
_BENCH_AVERAGES=()
2323

24-
benchmark::run_function bench_sleep 2 1
24+
benchmark::run_function bench_sleep 2 1 ""
2525

2626
assert_same "bench_sleep" "${_BENCH_NAMES[0]}"
2727
assert_same "2" "${_BENCH_REVS[0]}"

0 commit comments

Comments
 (0)