testfs

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package testfs provides a fault-injection wrapper around *os.File for use in crash-safety and durability tests of WAL, snapshot, and checkpoint paths.

A FaultFile is created via New and honours the fault modes configured in Faults:

  • Faults.FailWritesAfterBytes — returns an error once the cumulative bytes written reaches the threshold (simulates a partial write or disk failure mid-frame).
  • Faults.ReturnENOSPC — makes every Write call return syscall.ENOSPC regardless of bytes written.
  • Faults.FsyncDelay — sleeps for the given duration before each Sync call (simulates a slow or stalled fsync).
  • Faults.FailSyncAfter — lets the first N Sync calls succeed, then fails every subsequent call with ErrSyncFailed and discards the un-synced suffix of the file (simulates an fsync(2) failure where the kernel drops the dirty pages).
  • Faults.ReturnEIOOnSync — makes every Sync call fail with ErrSyncFailed under the same discard semantics.
  • Faults.CorruptOnRead — when non-nil, is called with the current file offset and the number of bytes about to be read; returning true flips the first byte of the result (simulates a bit-flip or CRC corruption at the given position).

FaultFile implements File, the minimal filesystem interface accepted by store/wal.OpenWith and store/snapshot write paths. File is purposely narrow so it can be satisfied by both *os.File and *FaultFile without importing "os" in tests.

Concurrency: FaultFile is safe for concurrent Read/Write/Seek/ Sync/Truncate/Close calls; all mutations serialise on an internal mutex. The underlying *os.File's own locking therefore plays no role; the wrapper is the serialising layer.

Index

Constants

This section is empty.

Variables

View Source
var ErrPartialWrite = fmt.Errorf("testfs: write budget exhausted: %w", io.ErrShortWrite)

ErrPartialWrite is returned by Write once Faults.FailWritesAfterBytes has been reached. It wraps io.ErrShortWrite so callers that already handle short writes behave correctly.

View Source
var ErrSyncFailed = fmt.Errorf("testfs: sync fault injected: %w", syscall.EIO)

ErrSyncFailed is returned by Sync once a sync fault mode (Faults.FailSyncAfter or Faults.ReturnEIOOnSync) fires. It wraps syscall.EIO so callers that classify fsync errors by errno behave as they would for a real I/O failure.

Functions

func IsENOSPC

func IsENOSPC(err error) bool

IsENOSPC reports whether err is (or wraps) a ENOSPC error.

Types

type FaultFile

type FaultFile struct {
	// contains filtered or unexported fields
}

FaultFile wraps an *os.File with configurable fault injection. Zero-value is invalid; always create via New.

FaultFile is safe for concurrent use; all operations are serialised on an internal mutex.

func New

func New(path string, faults Faults) (*FaultFile, error)

New opens or creates the file at path (flags: O_RDWR|O_CREATE) with the given Faults configuration.

func Wrap

func Wrap(f *os.File, faults Faults) *FaultFile

Wrap creates a FaultFile over an already-open *os.File. The caller must not use f directly after this call; FaultFile takes exclusive ownership.

func (*FaultFile) BudgetExhausted

func (ff *FaultFile) BudgetExhausted() bool

BudgetExhausted reports whether the FailWritesAfterBytes budget has been reached.

func (*FaultFile) Close

func (ff *FaultFile) Close() error

Close releases the underlying OS file.

func (*FaultFile) Read

func (ff *FaultFile) Read(p []byte) (int, error)

Read implements io.Reader. It honours Faults.CorruptOnRead by flipping the MSB of the first byte when the callback returns true for the current offset and read length.

func (*FaultFile) ResetWritten

func (ff *FaultFile) ResetWritten()

ResetWritten resets the cumulative-bytes counter and re-enables writes after a previous FailWritesAfterBytes fault. This allows a test to confirm a partial-write scenario and then continue writing to the same file for a second phase.

func (*FaultFile) Seek

func (ff *FaultFile) Seek(offset int64, whence int) (int64, error)

Seek implements io.Seeker and keeps the internal offset in sync.

func (*FaultFile) Sync

func (ff *FaultFile) Sync() error

Sync flushes to durable storage. It sleeps for Faults.FsyncDelay before delegating to the OS. When a sync fault mode fires (Faults.ReturnEIOOnSync, or Faults.FailSyncAfter exhausted) the un-synced suffix of the file is discarded and ErrSyncFailed is returned instead of delegating.

func (*FaultFile) Truncate

func (ff *FaultFile) Truncate(size int64) error

Truncate resizes the file to size bytes.

func (*FaultFile) Unwrap

func (ff *FaultFile) Unwrap() *os.File

Unwrap returns the underlying *os.File. Callers must not use the raw file concurrently with FaultFile methods.

func (*FaultFile) Write

func (ff *FaultFile) Write(p []byte) (int, error)

Write implements io.Writer. It respects Faults.ReturnENOSPC and Faults.FailWritesAfterBytes; a partial write is returned when the budget is crossed mid-call so the caller observes a (n < len(p), ErrPartialWrite) pair that mirrors a real OS short write.

func (*FaultFile) Written

func (ff *FaultFile) Written() int64

Written returns the cumulative bytes committed to the underlying file since the FaultFile was created.

type Faults

type Faults struct {
	// FailWritesAfterBytes causes Write to fail once the cumulative
	// bytes written to the file reaches this value. The partial write
	// up to the threshold is permitted; subsequent writes return
	// [ErrPartialWrite]. Zero disables this mode.
	FailWritesAfterBytes int64

	// ReturnENOSPC causes every Write call to return [syscall.ENOSPC]
	// regardless of the current write budget.
	ReturnENOSPC bool

	// FsyncDelay inserts a sleep of this duration before each Sync
	// call. Zero disables the delay.
	FsyncDelay time.Duration

	// FailSyncAfter causes Sync to fail once the given number of
	// calls have succeeded: the first FailSyncAfter Sync calls are
	// honoured, every subsequent call returns [ErrSyncFailed]. When
	// the fault fires, the bytes written since the last successful
	// Sync are discarded (the file is truncated back to its
	// durably-synced size). This mirrors the post-"fsyncgate" kernel
	// contract — after fsync(2) reports an error the previously-dirty
	// pages are marked clean and their content must be assumed lost —
	// so the file is left holding exactly the durable prefix a crash
	// would preserve. Zero disables this mode; use
	// [Faults.ReturnEIOOnSync] to fail every call.
	FailSyncAfter int

	// ReturnEIOOnSync causes every Sync call to return
	// [ErrSyncFailed] immediately, with the same
	// discard-unsynced-suffix semantics as [Faults.FailSyncAfter].
	ReturnEIOOnSync bool

	// CorruptOnRead, when non-nil, is called with the current file
	// offset and the number of bytes about to be read. Returning true
	// flips the MSB of the first byte in the result buffer to simulate
	// a bit-flip or CRC-corrupting storage error.
	CorruptOnRead func(offset, n int64) bool
}

Faults configures the injected failure modes for a FaultFile. The zero value disables all fault injection (the wrapper is a transparent pass-through).

type File

type File interface {
	io.ReadWriter
	io.Seeker
	// Sync flushes the OS write buffer to durable storage (equivalent
	// to fsync(2)).
	Sync() error
	// Truncate resizes the file to size bytes. If size < current
	// length, the suffix is discarded; if size > current length, the
	// file is extended with zero bytes (implementation-defined).
	Truncate(size int64) error
	// Close releases any associated OS resources.
	Close() error
}

File is the minimal filesystem interface used by store/wal and store/snapshot for write paths. *os.File and *FaultFile both implement this interface so production code and tests can accept either without conditional compilation.

File is safe for concurrent use by multiple goroutines (both *os.File and *FaultFile serialise their operations internally).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL