Skip to content

feat: FileIoOps fault-injection seam; close FileActionLog/FileOfflineQueue I/O gaps - #104

Merged
Yaraslaut merged 3 commits into
masterfrom
feature/file-io-fault-injection-seam
Aug 15, 2026
Merged

feat: FileIoOps fault-injection seam; close FileActionLog/FileOfflineQueue I/O gaps#104
Yaraslaut merged 3 commits into
masterfrom
feature/file-io-fault-injection-seam

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Closes #97 for real, and the identical gap in
FileOfflineQueue (found during the coverage sweep -- the same class of
branch was flagged there too, blocked on the same missing seam).

What this adds

morph::core::FileIoOps (include/morph/core/file_io_ops.hpp): the raw
fwrite/fflush/fsync/fopen/file-open/resize_file calls both
FileActionLog and FileOfflineQueue make, as an injectable strategy.
Every member defaults to the real syscall it stands in for, so a
default-constructed FileIoOps -- and every existing call site that
never passes one -- is byte-for-byte what was called directly before
this seam existed. No behavior change for any real caller.

Both classes' constructors now take an optional second FileIoOps
parameter (default {}), and every raw I/O call routes through it.

Branches closed

FileActionLog (morph#97's own list):

  • append()'s short-fwrite() throw
  • flush()'s failing fflush()/fsync() (and forgetting the
    unflushed idempotencyKeys on either)
  • rotate()'s pre-rotation failing fflush()/fsync() (nothing
    closed or renamed) and its failing reopen after a successful rename
    -- the one scenario morph#97 called out as needing the most
    real-world contortion to reach: requireOpen()'s throwing arm and
    the destructor's null check are both reachable from this single
    scenario, and now covered by one test
  • repairTornTail()'s unreadable-path and failing-resize_file() arms

FileOfflineQueue (same class of gap, found this session):

  • the constructor's own append-mode fopen() failing
  • enqueue()'s short fwrite(), failing fflush(), failing fsync()
  • construction-time compact()'s own short fwrite() and failing
    fflush()

14 new fault-injection tests total (8 + 6), each overriding exactly one
FileIoOps member and leaving the rest at their real defaults -- the
surrounding I/O still touches the real filesystem normally, and a retry
after clearing the injected failure was verified to actually succeed
and be durably recorded.

Verification

Full morph_tests suite: 1044 test cases, 10034 assertions, all
passing
-- both before this change (proving no regression for
existing callers) and after (with the new tests included).

docs/spec/journal/journal.md and docs/spec/offline/offline.md
updated for both constructors' new signature, per CLAUDE.md.

Yaraslau Tamashevich added 3 commits August 15, 2026 22:52
… gaps

Adds morph::core::FileIoOps (include/morph/core/file_io_ops.hpp): the raw
fwrite/fflush/fsync/fopen/file-open/resize_file calls FileActionLog and
FileOfflineQueue both make, as an injectable strategy. Every member
defaults to the real syscall it stands in for, so a default-constructed
FileIoOps -- and every existing call site that never passes one -- is
byte-for-byte what was called directly before this seam existed. No
behavior change for any real caller.

Wires it through FileActionLog (constructor takes an optional second
FileIoOps parameter, default {}) and closes every branch
#97 requested this exact seam for:
- append()'s short-fwrite() throw
- flush()'s failing fflush()/fsync() throws (and forgetting the
  unflushed idempotencyKeys on either)
- rotate()'s pre-rotation failing fflush()/fsync() (nothing closed or
  renamed) and its failing reopen after a successful rename (leaves
  the log closed; requireOpen()'s throwing arm and the destructor's
  null check are both reachable from this one scenario)
- repairTornTail()'s unreadable-path and failing resize_file() arms

8 new fault-injection tests in test_action_log_phase2.cpp, each
overriding exactly one FileIoOps member and leaving the rest at their
real defaults. Full morph_tests suite (1044 cases / 10034 assertions)
passes; the whole-file behavior around each injected failure was
verified to still work normally (a retry after clearing the injected
failure actually succeeds and is durably recorded).

docs/spec/journal/journal.md updated for the new constructor
signature, per CLAUDE.md.
…O gaps

Same shared seam FileActionLog now uses (morph::core::FileIoOps) --
FileOfflineQueue has the identical class of gap: several branches only
ran when a real OS-level file-I/O call failed partway through an
otherwise-successful operation. Constructor takes an optional second
FileIoOps parameter (default {}), and every fwrite/fflush/fsync/fopen
call site (writeLine/syncFile, and compact()'s own temp-file write)
now routes through it, syncFile changed from static to an instance
method since it reads _io.

6 new fault-injection tests in test_file_offline_queue.cpp: the
constructor's own append-mode fopen() failing, enqueue()'s short
fwrite()/failing fflush()/failing fsync(), and construction-time
compact()'s own short fwrite()/failing fflush(). Full morph_tests
suite (1044 cases / 10034 assertions) passes.

docs/spec/offline/offline.md updated for the new constructor
signature, per CLAUDE.md.
Was missing from the previous commits -- the Header <-> spec sync gate
correctly caught it (include/morph/core/file_io_ops.hpp is new, but no
docs/spec/core/** file changed alongside it). One file per public type,
per CLAUDE.md.
@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Yaraslaut
Yaraslaut merged commit b2e8e49 into master Aug 15, 2026
26 checks passed
@Yaraslaut
Yaraslaut deleted the feature/file-io-fault-injection-seam branch August 15, 2026 20:37
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.

FileActionLog: needs a fault-injection seam for file I/O failure paths (fwrite/fflush/fsync/fopen/repairTornTail)

1 participant