diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f62c1e4..b1962715 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +### Added +- `assert_file_permissions ` asserts a file's octal permission mode (`644`, `0755`); portable across GNU/BSD `stat` (#742) + ### Fixed - `watch` subcommand failed with `bashunit::watch::run: command not found` in the released binary because `src/watch.sh` was missing from the build; it is now bundled (#735) diff --git a/docs/assertions.md b/docs/assertions.md index 73194ff8..39a42817 100644 --- a/docs/assertions.md +++ b/docs/assertions.md @@ -830,6 +830,33 @@ function test_failure() { ``` ::: +## assert_file_permissions +> `assert_file_permissions "mode" "file"` + +Reports an error if `file` does not have the expected octal permission `mode` +(e.g. `644`, `0755`). A leading zero is optional (`0755` and `755` are equal). +Works on both Linux (GNU `stat`) and macOS (BSD `stat`). + +::: code-group +```bash [Example] +function test_success() { + local file="/tmp/file-path.txt" + touch "$file" + chmod 600 "$file" + + assert_file_permissions "600" "$file" +} + +function test_failure() { + local file="/tmp/file-path.txt" + touch "$file" + chmod 644 "$file" + + assert_file_permissions "600" "$file" +} +``` +::: + ## assert_is_file > `assert_is_file "file"` diff --git a/src/assert_files.sh b/src/assert_files.sh index 27fab431..aeca0048 100644 --- a/src/assert_files.sh +++ b/src/assert_files.sh @@ -149,3 +149,54 @@ function assert_file_not_contains() { bashunit::state::add_assertions_passed } + +## +# Normalizes an octal file mode to its decimal value, dropping leading zeros +# (so "0755" and "755" compare equal). Echoes nothing on invalid octal input. +# Arguments: $1 - octal mode string +## +function bashunit::assert::_octal_to_decimal() { + local mode="$1" + case "$mode" in + '' | *[!0-7]*) return 1 ;; + esac + printf '%d' "$((8#$mode))" +} + +## +# Asserts a file has the expected octal permission mode (e.g. "644", "0755"). +# Arguments: $1 - expected octal mode, $2 - file path +## +function assert_file_permissions() { + bashunit::assert::should_skip && return 0 + + local expected="$1" + local file="$2" + local test_fn + test_fn="$(bashunit::helper::find_test_function_name)" + local label + label="$(bashunit::helper::normalize_test_function_name "$test_fn")" + + if [ ! -e "$file" ]; then + bashunit::assert::mark_failed + bashunit::console_results::print_failed_test \ + "${label}" "${file}" "to have permissions ${expected}" "but the file does not exist" + return + fi + + local actual + actual="$(stat -c '%a' "$file" 2>/dev/null || stat -f '%Lp' "$file" 2>/dev/null)" + + local expected_dec actual_dec + expected_dec="$(bashunit::assert::_octal_to_decimal "$expected")" + actual_dec="$(bashunit::assert::_octal_to_decimal "$actual")" + + if [ "$expected_dec" != "$actual_dec" ]; then + bashunit::assert::mark_failed + bashunit::console_results::print_failed_test \ + "${label}" "${file}" "to have permissions ${expected}" "but got ${actual}" + return + fi + + bashunit::state::add_assertions_passed +} diff --git a/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_all_assert_docs.snapshot b/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_all_assert_docs.snapshot index e8232e44..3657a9d4 100644 --- a/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_all_assert_docs.snapshot +++ b/tests/acceptance/snapshots/bashunit_test_sh.test_bashunit_should_display_all_assert_docs.snapshot @@ -340,6 +340,15 @@ Reports an error if `file` does not contains the search string. - assert_file_not_contains is the inverse of this assertion and takes the same arguments. +## assert_file_permissions +-------------- +> `assert_file_permissions "mode" "file"` + +Reports an error if `file` does not have the expected octal permission `mode` +(e.g. `644`, `0755`). A leading zero is optional (`0755` and `755` are equal). +Works on both Linux (GNU `stat`) and macOS (BSD `stat`). + + ## assert_is_file -------------- > `assert_is_file "file"` @@ -589,3 +598,5 @@ Reports an error if `command` completes in `threshold_ms` milliseconds or less. Unambiguously reports an error message. Useful for reporting specific message when testing situations not covered by any `assert_*` functions. + + diff --git a/tests/unit/file_test.sh b/tests/unit/file_test.sh index 848b5686..9fc424af 100644 --- a/tests/unit/file_test.sh +++ b/tests/unit/file_test.sh @@ -190,3 +190,62 @@ function test_fails_assert_file_not_contains() { rm "$file" } + +# shellcheck disable=SC2155 +function test_successful_assert_file_permissions() { + # Windows (Git Bash) fakes POSIX permissions, so chmod/stat are unreliable there. + if bashunit::check_os::is_windows; then + bashunit::skip && return + fi + + local file="/tmp/test_successful_assert_file_permissions_$$" + touch "$file" + chmod 600 "$file" + + assert_empty "$(assert_file_permissions "600" "$file")" + + rm "$file" +} + +# shellcheck disable=SC2155 +function test_successful_assert_file_permissions_accepts_leading_zero() { + if bashunit::check_os::is_windows; then + bashunit::skip && return + fi + + local file="/tmp/test_assert_file_permissions_leading_zero_$$" + touch "$file" + chmod 755 "$file" + + assert_empty "$(assert_file_permissions "0755" "$file")" + + rm "$file" +} + +# shellcheck disable=SC2155 +function test_unsuccessful_assert_file_permissions() { + if bashunit::check_os::is_windows; then + bashunit::skip && return + fi + + local file="/tmp/test_unsuccessful_assert_file_permissions_$$" + touch "$file" + chmod 600 "$file" + + assert_same \ + "$(bashunit::console_results::print_failed_test \ + "Unsuccessful assert file permissions" "$file" "to have permissions 644" "but got 600")" \ + "$(assert_file_permissions "644" "$file")" + + rm "$file" +} + +function test_unsuccessful_assert_file_permissions_when_file_does_not_exist() { + local file="a_random_file_that_will_not_exist" + + assert_same \ + "$(bashunit::console_results::print_failed_test \ + "Unsuccessful assert file permissions when file does not exist" \ + "$file" "to have permissions 644" "but the file does not exist")" \ + "$(assert_file_permissions "644" "$file")" +}