faultbackend

package
v0.40.1 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package faultbackend wraps a backend.Backend so a test can make it misbehave: fail chosen operations, and suspend one operation until another reaches an agreed point.

The second capability is the reason the package exists. A distributed-storage bug is usually an interleaving, not an error path, and reproducing one with sleeps yields a flaky test that proves nothing on a loaded CI machine. A Gate blocks the matching operation inside the backend until the test releases it, so the interleaving is stated in the test rather than raced for.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Backend

type Backend struct {
	backend.Backend
	// contains filtered or unexported fields
}

Backend is a backend.Backend that applies [Rule]s to the operations passing through it, and records them. The zero value is not usable; call Wrap.

It deliberately forwards none of the optional backend capabilities (backend.Viewer, backend.Sizer, ReaderAt, ObjectCreator): every one of them has a mandatory fallback, so a wrapped backend exercises the same code as an unwrapped one, only slower.

func Wrap

func Wrap(b backend.Backend) *Backend

Wrap returns b with fault injection attached.

func (*Backend) Add

func (b *Backend) Add(r Rule) *Backend

Add installs a rule. Rules are consulted in the order they were added, and the first one to match an operation decides it.

func (*Backend) CompareAndSwap

func (b *Backend) CompareAndSwap(
	ctx context.Context, key string, expected backend.Version, data []byte,
) (backend.Version, bool, error)

CompareAndSwap implements backend.Backend. Gating it is how a test states the interleaving the commit protocol turns on: one writer suspended inside its conditional write while another commits over it.

func (*Backend) Count

func (b *Backend) Count(match func(Op) bool) int

Count returns how many recorded operations satisfy match.

func (*Backend) Delete

func (b *Backend) Delete(ctx context.Context, key string) error

Delete implements backend.Backend.

func (*Backend) List

func (b *Backend) List(ctx context.Context, prefix string) ([]string, error)

List implements backend.Backend.

func (*Backend) Ops

func (b *Backend) Ops() []Op

Ops returns the operations performed so far, in order.

func (*Backend) PutIfAbsent

func (b *Backend) PutIfAbsent(ctx context.Context, key string, data []byte) (bool, error)

PutIfAbsent implements backend.Backend.

func (*Backend) Read

func (b *Backend) Read(ctx context.Context, key string) ([]byte, error)

Read implements backend.Backend.

func (*Backend) ReadVersioned

func (b *Backend) ReadVersioned(ctx context.Context, key string) ([]byte, backend.Version, error)

ReadVersioned implements backend.Backend.

func (*Backend) Reset

func (b *Backend) Reset()

Reset removes every rule, leaving the recorded operations in place.

func (*Backend) Write

func (b *Backend) Write(ctx context.Context, key string, data []byte) error

Write implements backend.Backend.

type Gate

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

Gate suspends the backend operations matching it until the test releases them, so a test can state an interleaving instead of racing for it: arrange for the operation to arrive, drive the other goroutine to the point of interest, then release.

A gated operation blocks the goroutine that issued it inside the backend, so the code under test needs no hooks of its own.

func NewGate

func NewGate() *Gate

NewGate returns a gate with no operation suspended.

func (*Gate) Await

func (g *Gate) Await(tb testing.TB) Op

Await blocks until an operation is suspended at the gate and returns it, failing the test if none arrives.

func (*Gate) Release

func (g *Gate) Release()

Release lets every operation held at the gate, and every later one matching it, proceed. It is safe to call once; a second call panics.

func (*Gate) Rule

func (g *Gate) Rule(kind Kind, match func(Op) bool) Rule

Rule returns a Rule that suspends the first operation of kind that match accepts. A nil match takes the first operation of that kind.

type Kind

type Kind int

Kind is the backend operation a Rule matches.

const (
	Read Kind = iota
	Write
	PutIfAbsent
	CompareAndSwap
	ReadVersioned
	List
	Delete
)

The backend operations a rule can match.

func (Kind) String

func (k Kind) String() string

String implements fmt.Stringer.

type Op

type Op struct {
	Kind Kind
	Key  string
}

Op is a single backend operation offered to a Rule.

type Rule

type Rule struct {
	Kind  Kind
	Match func(Op) bool
	// Err, when non-nil, is returned instead of performing the operation.
	Err error
	// Before, when non-nil, runs before the operation. It may block, which is what suspends the
	// calling goroutine inside the backend (see [Gate]).
	Before func(Op)
	// Replace, when non-nil, rewrites the bytes a read returns. It models the failure a returned
	// error cannot: a store that hands back data which is not what was written, and says nothing.
	// It applies to [Read] alone; the wrapper implements no [backend.Viewer], so every read of an
	// object's bytes — including one made through [backend.ReadView] — passes through it.
	Replace func(Op, []byte) []byte
	// Times limits how many operations the rule applies to. Zero ⇒ unlimited.
	Times int
	// contains filtered or unexported fields
}

Rule decides what happens to the operations it matches. A rule with no Match matches every operation of its Kind.

Jump to

Keyboard shortcuts

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