Skip to content

Commit a1241a0

Browse files
authored
Merge pull request #593 from TypedDevs/feat/assert-duration
feat(assert): add duration assertions
2 parents 595268d + 9db337c commit a1241a0

File tree

5 files changed

+179
-0
lines changed

5 files changed

+179
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Add `assert_have_been_called_nth_with` for verifying arguments on the Nth invocation of a spy
77
- Add `assert_string_matches_format` and `assert_string_not_matches_format` with format placeholders (`%d`, `%s`, `%f`, `%i`, `%x`, `%e`, `%%`)
88
- Add JSON assertions: `assert_json_key_exists`, `assert_json_contains`, `assert_json_equals` (requires `jq`)
9+
- Add duration assertions: `assert_duration`, `assert_duration_less_than`, `assert_duration_greater_than`
910

1011
### Changed
1112
- Split Windows CI test jobs into parallel chunks to avoid timeouts

docs/assertions.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,57 @@ function test_failure() {
12701270
```
12711271
:::
12721272
1273+
## assert_duration
1274+
> `assert_duration "command" threshold_ms`
1275+
1276+
Reports an error if `command` takes longer than `threshold_ms` milliseconds to execute. Uses the framework's portable clock internally.
1277+
1278+
::: code-group
1279+
```bash [Example]
1280+
function test_success() {
1281+
assert_duration "echo hello" 500
1282+
}
1283+
1284+
function test_failure() {
1285+
assert_duration "sleep 2" 1000
1286+
}
1287+
```
1288+
:::
1289+
1290+
## assert_duration_less_than
1291+
> `assert_duration_less_than "command" threshold_ms`
1292+
1293+
Reports an error if `command` takes `threshold_ms` milliseconds or more to execute. Stricter than [assert_duration](#assert-duration) which allows equal values.
1294+
1295+
::: code-group
1296+
```bash [Example]
1297+
function test_success() {
1298+
assert_duration_less_than "echo hello" 500
1299+
}
1300+
1301+
function test_failure() {
1302+
assert_duration_less_than "sleep 2" 1000
1303+
}
1304+
```
1305+
:::
1306+
1307+
## assert_duration_greater_than
1308+
> `assert_duration_greater_than "command" threshold_ms`
1309+
1310+
Reports an error if `command` completes in `threshold_ms` milliseconds or less. Useful for verifying that a command takes at least a minimum amount of time.
1311+
1312+
::: code-group
1313+
```bash [Example]
1314+
function test_success() {
1315+
assert_duration_greater_than "sleep 1" 500
1316+
}
1317+
1318+
function test_failure() {
1319+
assert_duration_greater_than "echo hello" 5000
1320+
}
1321+
```
1322+
:::
1323+
12731324
## bashunit::fail
12741325
> `bashunit::fail "failure message"`
12751326

src/assert_duration.sh

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env bash
2+
3+
function bashunit::duration::measure_ms() {
4+
local command="$1"
5+
6+
local start_ns
7+
start_ns=$(bashunit::clock::now)
8+
9+
eval "$command" >/dev/null 2>&1
10+
11+
local end_ns
12+
end_ns=$(bashunit::clock::now)
13+
14+
local elapsed_ms
15+
elapsed_ms=$(bashunit::math::calculate "($end_ns - $start_ns) / 1000000" | awk '{printf "%.0f", $1}')
16+
17+
echo "$elapsed_ms"
18+
}
19+
20+
function assert_duration() {
21+
bashunit::assert::should_skip && return 0
22+
23+
local command="$1"
24+
local threshold_ms="$2"
25+
26+
local elapsed_ms
27+
elapsed_ms=$(bashunit::duration::measure_ms "$command")
28+
29+
if [ "$elapsed_ms" -gt "$threshold_ms" ]; then
30+
local test_fn
31+
test_fn="$(bashunit::helper::find_test_function_name)"
32+
local label
33+
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
34+
bashunit::assert::mark_failed
35+
bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to complete within (ms)" "${command}"
36+
return
37+
fi
38+
39+
bashunit::state::add_assertions_passed
40+
}
41+
42+
function assert_duration_less_than() {
43+
bashunit::assert::should_skip && return 0
44+
45+
local command="$1"
46+
local threshold_ms="$2"
47+
48+
local elapsed_ms
49+
elapsed_ms=$(bashunit::duration::measure_ms "$command")
50+
51+
if [ "$elapsed_ms" -ge "$threshold_ms" ]; then
52+
local test_fn
53+
test_fn="$(bashunit::helper::find_test_function_name)"
54+
local label
55+
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
56+
bashunit::assert::mark_failed
57+
bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to complete within (ms)" "${command}"
58+
return
59+
fi
60+
61+
bashunit::state::add_assertions_passed
62+
}
63+
64+
function assert_duration_greater_than() {
65+
bashunit::assert::should_skip && return 0
66+
67+
local command="$1"
68+
local threshold_ms="$2"
69+
70+
local elapsed_ms
71+
elapsed_ms=$(bashunit::duration::measure_ms "$command")
72+
73+
if [ "$elapsed_ms" -le "$threshold_ms" ]; then
74+
local test_fn
75+
test_fn="$(bashunit::helper::find_test_function_name)"
76+
local label
77+
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
78+
bashunit::assert::mark_failed
79+
bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to take at least (ms)" "${command}"
80+
return
81+
fi
82+
83+
bashunit::state::add_assertions_passed
84+
}

src/assertions.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
source "$BASHUNIT_ROOT_DIR/src/assert.sh"
44
source "$BASHUNIT_ROOT_DIR/src/assert_arrays.sh"
5+
source "$BASHUNIT_ROOT_DIR/src/assert_duration.sh"
56
source "$BASHUNIT_ROOT_DIR/src/assert_files.sh"
67
source "$BASHUNIT_ROOT_DIR/src/assert_folders.sh"
78
source "$BASHUNIT_ROOT_DIR/src/assert_json.sh"

tests/unit/assert_duration_test.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env bash
2+
# shellcheck disable=SC2329
3+
4+
function test_successful_assert_duration_within() {
5+
assert_empty "$(assert_duration "sleep 0" 1000)"
6+
}
7+
8+
function test_successful_assert_duration_within_fast_command() {
9+
assert_empty "$(assert_duration "echo hello" 500)"
10+
}
11+
12+
function test_unsuccessful_assert_duration_exceeds_threshold() {
13+
assert_same \
14+
"$(bashunit::console_results::print_failed_test \
15+
"Unsuccessful assert duration exceeds threshold" \
16+
"1000" "to complete within (ms)" "sleep 1")" \
17+
"$(assert_duration "sleep 1" 1000)"
18+
}
19+
20+
function test_successful_assert_duration_less_than() {
21+
assert_empty "$(assert_duration_less_than "sleep 0" 1000)"
22+
}
23+
24+
function test_unsuccessful_assert_duration_less_than() {
25+
assert_same \
26+
"$(bashunit::console_results::print_failed_test \
27+
"Unsuccessful assert duration less than" \
28+
"100" "to complete within (ms)" "sleep 1")" \
29+
"$(assert_duration_less_than "sleep 1" 100)"
30+
}
31+
32+
function test_successful_assert_duration_greater_than() {
33+
assert_empty "$(assert_duration_greater_than "sleep 1" 500)"
34+
}
35+
36+
function test_unsuccessful_assert_duration_greater_than() {
37+
assert_same \
38+
"$(bashunit::console_results::print_failed_test \
39+
"Unsuccessful assert duration greater than" \
40+
"5000" "to take at least (ms)" "echo hello")" \
41+
"$(assert_duration_greater_than "echo hello" 5000)"
42+
}

0 commit comments

Comments
 (0)