Skip to content

test: cover docscheck's filesystem-error branches with an error-injecting fs.FS - #20

Merged
scottescue merged 2 commits into
mainfrom
issue-15
Aug 31, 2026
Merged

test: cover docscheck's filesystem-error branches with an error-injecting fs.FS#20
scottescue merged 2 commits into
mainfrom
issue-15

Conversation

@charles-fineman

@charles-fineman charles-fineman commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Closes #15

Summary

fstest.MapFS cannot produce a filesystem error — every path in one either exists or is cleanly absent — so a set of error branches in internal/docscheck had no test that could reach them. This adds an fs.FS that fails on demand and pins all three, plus the invariant that bounds them.

Those branches are what keep the gate's silence honest: an unreadable tree that collected no documents reads exactly like a tree with no drift, so a check that swallowed the error would fail open on the one input it most needs to reject.

What changed

internal/docscheck/docscheck_test.go only. No production code changed — this PR fixes no bug and alters no behavior.

  • unreadableRepository — embeds the existing repository(nil) fixture and returns a sentinel error from ReadDir for one named directory. ReadDir alone reaches every target branch: fs.WalkDir reads each directory as it descends, and rootEntries reads the root directly. Open, Stat, and file contents come from the embedded fixture unchanged.

  • TestAnUnreadableTreeIsReportedRatherThanIgnored — five subtests:

    Subtest Branch
    a directory that fails part way through the walk DocumentFilesWalkDir callback error path + walk's error return
    a repository root that cannot be listed same, failing at the root
    a root that stops CheckAll at document collection CheckAll collection-failure branch
    a root that CheckNamedPaths cannot list rootEntries / CheckNamedPaths fs.ReadDir(".") path
    a skipped directory, which is never read at all the boundary: fs.WalkDir offers a directory to the callback before reading it, so a skipped testdata/dist is never one the walk can fail on

Each subtest asserts the injected cause reaches the caller — by identity via errors.Is where the error is returned, by message fragment where a check turns it into a finding — so an error swallowed and replaced with a generic one would still fail.

CheckNamedPaths is called directly rather than through CheckAll, because the same unreadable root stops CheckAll at document collection first.

Verification

  • go test ./... and go test -race ./internal/docscheck/ green; gofmt -l . and go vet ./... clean.
  • Coverage of internal/docscheck: 95.4% → 96.8%. CheckAll, DocumentFiles, and rootEntries are now at 100%.
  • Mutation-checked, all four: swallowing the collect error, swallowing the rootEntries error, returning SkipDir instead of propagating in the walk callback, and dropping testdata from the skip list each make their own subtest fail and no other. The tests pin behavior, not line coverage.

Peer review

Security, Go, testing, and documentation reviews all approved. Two minor review findings were addressed in 256d295 rather than deferred: two subtest names that had broken the file's scenario-first phrasing, and the unpinned skip invariant. Deferred findings are captured in #22 (remaining content/parse edge-case branches) and #23 (CheckLinks mislabels an unreadable target as missing).

Acceptance criteria

  • An error-injecting fs.FS wrapper exists in the package's tests
  • The WalkDir, ReadDir, and CheckAll collection-failure branches are each exercised and assert a specific message
  • Coverage of internal/docscheck does not regress

Summary by CodeRabbit

  • Bug Fixes
    • Improved error reporting when documentation files or directories cannot be read.
    • Added coverage for unreadable repository roots, subdirectories, and named paths.

…tree

fstest.MapFS cannot produce a filesystem error: every path in one either
exists or is cleanly absent, so the branches that report a repository the
checks cannot read went unexercised. Those branches are the ones that keep
a check's silence honest — no documents collected reads exactly like no
drift found — so leaving them unpinned left the gate's failure mode
untested.

unreadableRepository wraps the existing fixture and fails ReadDir for one
named directory. ReadDir alone reaches all three branches: fs.WalkDir
reads each directory as it descends, and rootEntries reads the root
directly. Everything else is the embedded fixture, unchanged.

Four subtests pin the walk's mid-descent failure, the unlistable root,
CheckAll's collection failure, and CheckNamedPaths' unreadable root. Each
asserts the injected cause reaches the report rather than being replaced
with a generic one. Package coverage 95.4% -> 96.8%, with CheckAll,
DocumentFiles, and rootEntries now fully covered.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cd8eea6c-ba04-4c2f-b045-9cb66f3ba009

📥 Commits

Reviewing files that changed from the base of the PR and between f9f792f and 256d295.

📒 Files selected for processing (1)
  • internal/docscheck/docscheck_test.go

Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The tests add an error-injecting filesystem wrapper and cover unreadable directory and repository-root behavior in DocumentFiles, CheckAll, and CheckNamedPaths.

Changes

Docscheck filesystem error coverage

Layer / File(s) Summary
Error injection and collection failure tests
internal/docscheck/docscheck_test.go
Adds an fstest.MapFS wrapper that forces ReadDir errors. Tests verify error reporting, the absence of partial document results, and skipped unreadable directories across docscheck collection paths.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 256d2

This PR adds focused filesystem-error coverage without changing production behavior; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: scottescue

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #15. The tests add an error-injecting fs.FS, cover DocumentFiles walk and callback errors, CheckAll collection failures, rootEntries and CheckNamedPaths ReadDir errors, verif…
Out of Scope Changes check ✅ Passed The pull request changes only internal/docscheck test infrastructure and tests. All changes support the linked issue objectives.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding tests for docscheck filesystem-error branches with an error-injecting fs.FS.
Full details: Linked Issues check

Explanation

The changes satisfy issue #15. The tests add an error-injecting fs.FS, cover DocumentFiles walk and callback errors, CheckAll collection failures, rootEntries and CheckNamedPaths ReadDir errors, verify propagated errors, and preserve coverage.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-15

Comment @coderabbitai help to get the list of available commands.

Peer review noted that the three error cases have a boundary nothing
states: fs.WalkDir offers each directory to the callback before reading
it, so a directory DocumentFiles skips is never a directory it can fail
on. That is what keeps an unreadable testdata or dist from breaking a walk
that never wanted to enter them.

The order is load-bearing and invisible. Dropping testdata from the skip
list makes the new subtest fail and nothing else, so the invariant is now
pinned where a reader of the skip list will find it.

Also renames two subtests to the file's scenario-first phrasing, which
they had broken by opening with a bare function name.
@charles-fineman
charles-fineman marked this pull request as ready for review August 31, 2026 01:41
charles-fineman pushed a commit that referenced this pull request Aug 31, 2026
PR #20 was opened for #15 five minutes before this branch's pull request
and implements the same thing — an fs.FS that fails on ReadDir, exercising
the walk and collection error paths. Two sessions picked up the issue in
parallel.

Theirs was first and is scoped to #15 alone, where this branch only carried
those tests because #18 touches the same package. Removing them here
resolves the conflict in docscheck_test.go and leaves this branch as what
it should have been: the scan bound and nothing else.

The #18 work is untouched. Package coverage returns to 95.5%, with #20
carrying the rest.
@charles-fineman

Copy link
Copy Markdown
Collaborator Author

Deferring to this PR for #15 — it was opened five minutes before mine and is properly scoped to the issue.

I had picked up #15 alongside #18 because both touch internal/docscheck and separate branches would conflict in docscheck_test.go. My PR #21 carried an equivalent failingFS wrapper; I've removed it, so #21 is now the scan bound alone and the two no longer collide. Nothing here needs to change.

One thing from my version that may be worth folding in, if it isn't already covered: the branch where CheckAll treats a collection failure as "no documents found". That one matters more than the others, because it makes the entire gate pass vacuously on an unreadable tree — a check whose only job is to fail would stop failing, silently. I asserted it with a mutation (files, _ := DocumentFiles(fsys)) rather than trusting it.

Fourth duplicate dispatch tonight (#11, #9, #18, now #15). Worth raising with whoever runs the dispatcher: checking assignee and in-progress before dispatching would have caught all four — #15 was assigned and labelled at 01:32, three minutes before this PR was opened.

@charles-fineman

Copy link
Copy Markdown
Collaborator Author

All peer reviews addressed. Ready for final review @scottescue

Security, Go, testing, and documentation reviews all approved. Two minor findings were fixed in 256d295 rather than deferred — two subtest names that had broken the file's scenario-first phrasing, and the unpinned fs.WalkDir skip invariant. Deferred findings are captured in #22 and #23.

One note worth surfacing: grooming caught that the implementer guidance I originally wrote into #22 was wrong. It said the readDocument-failure branches need an Open that fails; they need ReadFile, because fstest.MapFS's own ReadFile promotes onto an embedding wrapper and satisfies fs.ReadFileFS, so an Open-only override intercepts nothing and the test passes for the wrong reason. Verified with a standalone probe and corrected in the issue. Nothing in this PR was affected — it overrides ReadDir, which fs.WalkDir and rootEntries do call.

@charles-fineman charles-fineman added the ready-for-merge Approved and ready to merge label Aug 31, 2026
@scottescue
scottescue merged commit 6b438d9 into main Aug 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge Approved and ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Testing] Cover docscheck's filesystem-error branches with an error-injecting fs.FS

2 participants