Skip to content

feat(assert): file readable/writable/executable and not-empty parity with directories #1024

Description

@Chemaclass

Problem

Directories and files are not treated equally. src/assert/folders.sh provides:

  • assert_is_directory_readable (:68) / assert_is_directory_not_readable (:81)
  • assert_is_directory_writable (:94) / assert_is_directory_not_writable (:107)
  • assert_is_directory_empty (:42) / assert_is_directory_not_empty (:55)

src/assert/files.sh provides none of the equivalents. It has assert_is_file_empty but no assert_is_file_not_empty, and no readable/writable/executable checks at all.

So testing that a generated script is executable, that a log file is writable, or that an output file is non-empty means dropping to assert_true test -x "$f" — which works since #994, but loses the specific failure message that is the whole reason the filesystem assertions exist.

Executability in particular is the single most common thing a shell project asserts about a file it produced.

Proposal

Add to src/assert/files.sh, following the existing shape of the folder assertions:

  • assert_is_file_readable / assert_is_file_not_readable
  • assert_is_file_writable / assert_is_file_not_writable
  • assert_is_file_executable / assert_is_file_not_executable
  • assert_is_file_not_empty

Failure messages should distinguish "path does not exist" from "exists but is not readable" — the folder assertions are the reference for wording.

Acceptance criteria

  • All seven assertions added, each with a passing and a failing test
  • A missing path fails with a message saying it does not exist, not "not readable"
  • A directory passed to assert_is_file_* fails with a message saying it is not a file (consistent with assert_is_file)
  • Symlinks follow through to the target, consistent with every filesystem assertion other than the assert_is_symlink family (feat(assert): symlink assertions — a link is currently indistinguishable from its target #981)
  • Windows: Git Bash fakes file permissions — guard the permission-dependent tests with the project's Windows skip, as tests/unit/assert/files_test.sh:197 already does
  • Tests do not run as root in CI in a way that makes "not writable" vacuously false; assert the guard or skip
  • assert_is_file_not_empty passes for a file with a single newline and fails for a zero-byte file

Repo checklist (agent)

  • TDD: RED → GREEN → REFACTOR. Use /add-assertion if helpful.
  • Bash 3.0+ only: no printf -v, no +=, no declare -A, no [[ ]], no ${var,,}, no &>>.
  • Assertions are bare-named (assert_x), helpers are bashunit::-namespaced. Start every assertion with bashunit::assert::should_skip && return 0 and report through bashunit::assertion_failed / bashunit::assertion_passed, matching its neighbours in the same file.
  • A missing required argument must go through bashunit::assert::usage_error, not compare against an empty string (feat(assert): a missing argument reports a failed assertion, not a usage error #983).
  • Tests: mirror the src layout — tests/unit/assert/<file>_test.sh. There is no assert_fails; test failure output by comparing against print_failed_test, as the existing tests do.
  • Docs: add the entry to docs/assertions.md, then regenerate the bashunit doc acceptance snapshot (editing that file breaks it).
  • Update the assertion count in README.md ("It ships 73 assertions…" — currently already stale at 74) and add a CHANGELOG.md line under ## Unreleased.
  • Gates: make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w.
  • One issue = one PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions