testfs

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 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.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.

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.

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

	// 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