testfs

package
v0.1.0 Latest Latest
Warning

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

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