Skip to content

fix(teststate): stop after reporting a fatal error - #1880

Merged
james00012 merged 2 commits into
mainfrom
fix/teststate-fatal-fallthrough
Aug 7, 2026
Merged

fix(teststate): stop after reporting a fatal error#1880
james00012 merged 2 commits into
mainfrom
fix/teststate-fatal-fallthrough

Conversation

@james00012

@james00012 james00012 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Found while reviewing #1879. Pre-existing.

Bug

Every t.Fatalf in core/teststate falls through to the code after it. With *testing.T that is invisible, because FailNow calls runtime.Goexit. But TestingT exists so other harnesses can be plugged in, and an implementation whose FailNow returns keeps going.

The harmful case is save:

bytes, err := json.Marshal(value)
if err != nil {
    t.Fatalf("Failed to convert value %s to JSON: %v", path, err)
}
...
os.WriteFile(path, bytes, 0o600)   // bytes is nil, writes a zero byte file

A later stage then loads that empty file. IsPresent and IsEmptyJSON were quieter but wrong the same way, answering "absent" or "empty" when the real answer was an error they had just announced.

Fix

An explicit return after each Fatalf, plus a package comment explaining why they are not dead code.

Tests

Three regression tests driven by a TestingT whose FailNow returns. The marshal one was confirmed to fail without the fix.

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling during test state saving, loading, and validation.
    • Prevented invalid or unreadable test state data from being incorrectly treated as missing or empty.
    • Ensured failed operations stop processing immediately and report errors accurately.
  • Tests

    • Added regression coverage for save failures, inaccessible paths, and invalid JSON.

Every t.Fatalf in this package fell through to the code after it. With
*testing.T that is invisible, since FailNow calls runtime.Goexit, but
TestingT exists so other harnesses can be plugged in and an implementation
whose FailNow returns kept going.

The harmful case was save: a marshal failure was reported and then
os.WriteFile ran anyway with a nil byte slice, leaving a zero byte file for
a later stage to load. IsPresent and IsEmptyJSON were quieter but wrong in
the same way, reporting absent or empty when the real answer was an error
they had just announced.

Add an explicit return after each Fatalf, with a package comment explaining
why they are not dead code.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change adds explicit returns after t.Fatalf calls in teststate helpers. New tests verify behavior when custom test reporters continue after failure, including marshal errors, unreadable paths, and invalid JSON.

Changes

Teststate error handling

Layer / File(s) Summary
Error-path return guards
modules/core/teststate/teststate.go
save, Load, IsPresent, and IsEmptyJSON now return immediately after reporting failures.
Failure handling regression tests
modules/core/teststate/teststate_test.go
Tests cover marshal failures without file creation, unreadable paths, and invalid JSON handling.

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

Possibly related PRs

Suggested reviewers: yhakbar, denis256

Poem

After failure, paths now cease,
No extra steps disturb the peace.
Bad JSON stays clearly known,
And failed saves leave no stone.
Teststate guards the flow with grace.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: stopping execution after reporting a fatal error.
Description check ✅ Passed The description explains the bug, fix, affected behavior, and regression tests, but omits the template’s release and TODO sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/teststate-fatal-fallthrough

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@james00012
james00012 marked this pull request as ready for review August 5, 2026 04:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
modules/core/teststate/teststate.go (1)

124-136: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve error state across boolean helpers.

When FailNow returns, IsEmptyJSON reports invalid JSON and returns false. IsPresent treats that value as “not empty” and returns true. An invalid file can therefore be marked present.

The same ambiguity makes save treat an IsPresent read failure as “absent” and continue toward os.WriteFile. Use an internal error-returning helper or another explicit error signal. Add regression tests through IsPresent and Save.

Also applies to: 157-158

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/core/teststate/teststate.go` around lines 124 - 136, Preserve
read/JSON-parse errors from the shared test-data validation used by IsEmptyJSON
and IsPresent instead of converting them to an ambiguous false result after
t.Fatalf. Update save to stop and propagate the same error when presence
checking fails, rather than treating the file as absent and calling
os.WriteFile; add regression coverage for both IsPresent and Save invalid-file
paths.
🧹 Nitpick comments (2)
modules/core/teststate/teststate.go (1)

10-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the package comment accurate.

The comment says every t.Fatalf has an explicit return. The os.WriteFile failure in save at Line [97] and the JSON unmarshal failure in Load at Line [115] do not. Add those returns, or narrow the comment to failure branches followed by more work.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/core/teststate/teststate.go` around lines 10 - 12, Update the failure
branches in save and Load so the os.WriteFile error and JSON unmarshal error
each return immediately after t.Fatalf, keeping the package comment accurate and
preventing execution from continuing in harnesses whose FailNow returns.
modules/core/teststate/teststate_test.go (1)

247-264: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for Load's read-failure guard.

The new reporter and tests cover Save, IsPresent, and IsEmptyJSON, but not Load. Before this return, a non-stopping reporter could let Load continue into json.Unmarshal and record a second failure. Add a test that uses an unreadable path and asserts that only the read failure is recorded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/core/teststate/teststate_test.go` around lines 247 - 264, Add a test
covering teststate.Load with an unreadable path, using the existing non-stopping
reporter pattern. Assert that the read failure is reported and only one failure
is recorded, confirming Load returns before attempting json.Unmarshal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@modules/core/teststate/teststate.go`:
- Around line 124-136: Preserve read/JSON-parse errors from the shared test-data
validation used by IsEmptyJSON and IsPresent instead of converting them to an
ambiguous false result after t.Fatalf. Update save to stop and propagate the
same error when presence checking fails, rather than treating the file as absent
and calling os.WriteFile; add regression coverage for both IsPresent and Save
invalid-file paths.

---

Nitpick comments:
In `@modules/core/teststate/teststate_test.go`:
- Around line 247-264: Add a test covering teststate.Load with an unreadable
path, using the existing non-stopping reporter pattern. Assert that the read
failure is reported and only one failure is recorded, confirming Load returns
before attempting json.Unmarshal.

In `@modules/core/teststate/teststate.go`:
- Around line 10-12: Update the failure branches in save and Load so the
os.WriteFile error and JSON unmarshal error each return immediately after
t.Fatalf, keeping the package comment accurate and preventing execution from
continuing in harnesses whose FailNow returns.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fa357bfb-a016-4ab9-b687-a40eecdc9128

📥 Commits

Reviewing files that changed from the base of the PR and between 5eea79a and 82c0c27.

📒 Files selected for processing (2)
  • modules/core/teststate/teststate.go
  • modules/core/teststate/teststate_test.go

@denis256 denis256 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, the package comment says every Fatalf has a return, but three calls at the end of functions are missing them:

  • Line 97 in teststate.go: save() (on os.WriteFile failure)
  • Line 115 in teststate.go: Load() (on json.Unmarshal failure)
  • Line 199 in teststate.go: Cleanup() (on os.Remove failure)

@james00012
james00012 merged commit 18eb952 into main Aug 7, 2026
17 checks passed
@james00012
james00012 deleted the fix/teststate-fatal-fallthrough branch August 7, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants