Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Add `assert_have_been_called_nth_with` for verifying arguments on the Nth invocation of a spy
- Add `assert_string_matches_format` and `assert_string_not_matches_format` with format placeholders (`%d`, `%s`, `%f`, `%i`, `%x`, `%e`, `%%`)
- Add JSON assertions: `assert_json_key_exists`, `assert_json_contains`, `assert_json_equals` (requires `jq`)
- Add duration assertions: `assert_duration`, `assert_duration_less_than`, `assert_duration_greater_than`

### Changed
- Split Windows CI test jobs into parallel chunks to avoid timeouts
Expand Down
51 changes: 51 additions & 0 deletions docs/assertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,57 @@ function test_failure() {
```
:::

## assert_duration
> `assert_duration "command" threshold_ms`

Reports an error if `command` takes longer than `threshold_ms` milliseconds to execute. Uses the framework's portable clock internally.

::: code-group
```bash [Example]
function test_success() {
assert_duration "echo hello" 500
}

function test_failure() {
assert_duration "sleep 2" 1000
}
```
:::

## assert_duration_less_than
> `assert_duration_less_than "command" threshold_ms`

Reports an error if `command` takes `threshold_ms` milliseconds or more to execute. Stricter than [assert_duration](#assert-duration) which allows equal values.

::: code-group
```bash [Example]
function test_success() {
assert_duration_less_than "echo hello" 500
}

function test_failure() {
assert_duration_less_than "sleep 2" 1000
}
```
:::

## assert_duration_greater_than
> `assert_duration_greater_than "command" threshold_ms`

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.

::: code-group
```bash [Example]
function test_success() {
assert_duration_greater_than "sleep 1" 500
}

function test_failure() {
assert_duration_greater_than "echo hello" 5000
}
```
:::

## bashunit::fail
> `bashunit::fail "failure message"`

Expand Down
84 changes: 84 additions & 0 deletions src/assert_duration.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash

function bashunit::duration::measure_ms() {
local command="$1"

local start_ns
start_ns=$(bashunit::clock::now)

eval "$command" >/dev/null 2>&1

local end_ns
end_ns=$(bashunit::clock::now)

local elapsed_ms
elapsed_ms=$(bashunit::math::calculate "($end_ns - $start_ns) / 1000000" | awk '{printf "%.0f", $1}')

echo "$elapsed_ms"
}

function assert_duration() {
bashunit::assert::should_skip && return 0

local command="$1"
local threshold_ms="$2"

local elapsed_ms
elapsed_ms=$(bashunit::duration::measure_ms "$command")

if [ "$elapsed_ms" -gt "$threshold_ms" ]; then
local test_fn
test_fn="$(bashunit::helper::find_test_function_name)"
local label
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
bashunit::assert::mark_failed
bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to complete within (ms)" "${command}"
return
fi

bashunit::state::add_assertions_passed
}

function assert_duration_less_than() {
bashunit::assert::should_skip && return 0

local command="$1"
local threshold_ms="$2"

local elapsed_ms
elapsed_ms=$(bashunit::duration::measure_ms "$command")

if [ "$elapsed_ms" -ge "$threshold_ms" ]; then
local test_fn
test_fn="$(bashunit::helper::find_test_function_name)"
local label
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
bashunit::assert::mark_failed
bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to complete within (ms)" "${command}"
return
fi

bashunit::state::add_assertions_passed
}

function assert_duration_greater_than() {
bashunit::assert::should_skip && return 0

local command="$1"
local threshold_ms="$2"

local elapsed_ms
elapsed_ms=$(bashunit::duration::measure_ms "$command")

if [ "$elapsed_ms" -le "$threshold_ms" ]; then
local test_fn
test_fn="$(bashunit::helper::find_test_function_name)"
local label
label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
bashunit::assert::mark_failed
bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to take at least (ms)" "${command}"
return
fi

bashunit::state::add_assertions_passed
}
1 change: 1 addition & 0 deletions src/assertions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

source "$BASHUNIT_ROOT_DIR/src/assert.sh"
source "$BASHUNIT_ROOT_DIR/src/assert_arrays.sh"
source "$BASHUNIT_ROOT_DIR/src/assert_duration.sh"
source "$BASHUNIT_ROOT_DIR/src/assert_files.sh"
source "$BASHUNIT_ROOT_DIR/src/assert_folders.sh"
source "$BASHUNIT_ROOT_DIR/src/assert_json.sh"
Expand Down
42 changes: 42 additions & 0 deletions tests/unit/assert_duration_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# shellcheck disable=SC2329

function test_successful_assert_duration_within() {
assert_empty "$(assert_duration "sleep 0" 1000)"
}

function test_successful_assert_duration_within_fast_command() {
assert_empty "$(assert_duration "echo hello" 500)"
}

function test_unsuccessful_assert_duration_exceeds_threshold() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert duration exceeds threshold" \
"1000" "to complete within (ms)" "sleep 1")" \
"$(assert_duration "sleep 1" 1000)"
}

function test_successful_assert_duration_less_than() {
assert_empty "$(assert_duration_less_than "sleep 0" 1000)"
}

function test_unsuccessful_assert_duration_less_than() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert duration less than" \
"100" "to complete within (ms)" "sleep 1")" \
"$(assert_duration_less_than "sleep 1" 100)"
}

function test_successful_assert_duration_greater_than() {
assert_empty "$(assert_duration_greater_than "sleep 1" 500)"
}

function test_unsuccessful_assert_duration_greater_than() {
assert_same \
"$(bashunit::console_results::print_failed_test \
"Unsuccessful assert duration greater than" \
"5000" "to take at least (ms)" "echo hello")" \
"$(assert_duration_greater_than "echo hello" 5000)"
}
Loading