threadtest

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package threadtest is the shared conformance table for thread.Storage backends — the executable form of ADR 0011 §5's promises. Every backend runs it before it may call itself a backend (the store's storetest precedent: LangGraph ships one table for its savers and got five backends out of it); Run takes a factory so each subtest gets fresh storage.

Index

Constants

View Source
const (
	CrashMatrixTurnID  = "s_mx_turn"  // prompt and turn_end
	CrashMatrixParkID  = "s_mx_park"  // approval, decision, compaction, resume_arm, decide_signed
	CrashMatrixSteerID = "s_mx_steer" // steer and clear_queue
	CrashMatrixPoolID  = "s_mx_pool"  // pool_receipt
	CrashMatrixForkID  = "s_mx_fork"  // fork and fork_settle (the forked session's own id)
	CrashMatrixKidID   = "s_mx_kid"   // the pool points' child session
)

The four sessions the child creates, one per family of write points; the parent asserts over each by id.

View Source
const CrashSession = "s_crashturn"

CrashSession is the session id the child creates.

Variables

This section is empty.

Functions

func CrashMatrix

func CrashMatrix(t *testing.T, helperTest string, pathFor func(point string) string, open func(path string) (thread.Storage, error))

CrashMatrix runs the parent side over every write point: for each, it re-executes the test binary at helperTest (the backend's child, gated on the crash env) with the point set and its own fresh storage location (pathFor — the points must not share sessions), waits out the child, and asserts the point's invariant through reopen — a fresh Storage over the same location, the shape of the next process after the crash. open is the backend's constructor over a location.

func CrashTurn

func CrashTurn(t *testing.T, helperTest, storagePath string, reopen func() (thread.Storage, error))

CrashTurn runs the parent side: it re-executes the test binary at helperTest (the backend's child test, gated on the crash env), waits for the child's "step0done" marker, SIGKILLs it mid-second-step, and asserts the crash's durability through reopen — a fresh Storage over the same location. storagePath is passed to the child through WEFT_THREADTEST_CRASH_STORAGE.

func Run

func Run(t *testing.T, open func(t *testing.T) thread.Storage)

Run executes the conformance table against a backend. open returns fresh storage for each subtest; the table never shares state between them. The corruption rows — unknown kind, newer "v", malformed and torn lines, a header from a newer weft — need a backend that can hold undecodable data, so they run only when the storage also implements RawInjector (RawHeaderInjector for the header rows) and skip otherwise: Memory holds the raw bytes (it implements the hooks), and a backend that cannot hold them at all pins its loudness where its format lives. The capability rows run when the backend has the capability: Flusher and Releaser each get their row, a backend that implements thread.Leaser runs the RunLeaser table as the Leaser subtest, and one that implements thread.Watcher runs the whole RunWatch table as the Watch subtest. The one thing Run cannot reach is a second Storage over the same sessions — RunTwoWriters takes that factory.

func RunCrashMatrixChild

func RunCrashMatrixChild(t *testing.T, open func() (thread.Storage, error))

RunCrashMatrixChild is the child side, called from the backend's re-executed helper test: it opens its storage through open and dies at the point the env names. It returns only when the env gate is unset (the parent's own run).

func RunCrashTurnChild

func RunCrashTurnChild(t *testing.T, open func() (thread.Storage, error))

RunCrashTurnChild is the child side, called from the backend's re-executed helper test: it opens its storage through open (the WEFT_THREADTEST_CRASH_STORAGE location), runs the turn, prints the step0done marker once the first step is fully emitted, and blocks in the second model call until the parent kills it. It returns only when the env gate is unset (the parent's own run).

func RunLeaser

func RunLeaser(t *testing.T, open func(t *testing.T) thread.Storage)

RunLeaser is the conformance sub-table for the Leaser capability — the writer lease that keeps two Sessions on one Storage value to one writer (ADR 0011 §5). Run calls it for a backend that implements thread.Leaser: Acquire is idempotent for its holder and reports the session's complete entry lines; a second holder is refused with ErrLocked naming the session; Yield hands the lease over and never ends another holder's; Release and Delete through the Storage value end it; readers are never refused; and the count is the signal a stale writer is caught by — it follows every append, and counts neither a torn tail nor the header; beside it Acquire reports the header's Created, which tells a session created again under its id from the one it replaced. The Sessions subtest is RunOneWriter: the same rule as two Session values meet it.

func RunOneWriter

func RunOneWriter(t *testing.T, open func(t *testing.T) thread.Storage)

RunOneWriter is the Session-level half of the Leaser table: what the lease is for. Two Session values on one Storage value are kept to one writer — the first to write holds the session until its Close, every write of the other fails with ErrLocked and changes nothing — Create is a first write, Open and the reads lock nothing, a Session whose view fell behind is refused with ErrStale, and Delete through the holder's Storage takes the lease with the session. RunLeaser runs it as its Sessions subtest; the rows need nothing from the backend but thread.Leaser.

func RunTurns

func RunTurns(t *testing.T, open func(t *testing.T) thread.Storage)

RunTurns executes the session-level rows a backend must carry — the turn machinery's durable shapes, which are entries like any others but whose correctness depends on what the backend gives back on a reload: parent links that leave the append order (a resume's join attaches to an earlier entry), and receipts that are restored from the file alone. open returns fresh storage for each row. Backends call it beside Run.

func RunTwoWriters

func RunTwoWriters(t *testing.T, open func(t *testing.T) (first, second thread.Storage))

RunTwoWriters is the conformance sub-table for the one-writer rule (ADR 0011 §5), for backends that enforce it across Storage values: a backend opts in by providing open, which returns two independent Storages over the same sessions — two opens of one directory or one database, the in-process shape of two processes. The second writer is refused with ErrLocked naming the session (never a filesystem path), readers never lock, a held session cannot be created over or deleted by the other, and Delete frees the name. When the backend implements thread.Releaser the hold is a lease: Release hands the session to the other writer, and the first one is refused in turn until it is handed back. When it implements thread.Leaser, a holder on one Storage is refused on the other, and Yield hands over.

func RunWatch

func RunWatch(t *testing.T, open func(t *testing.T) thread.Storage)

RunWatch runs the Watch capability's conformance table against a backend that implements thread.Watcher (the optional interface, ADR 0011 §5); Run calls it for such a backend. The whole session yields in arrival order, after names the resume point, entries appended while watching arrive exactly once, a canceled context ends the stream, and the loud failures (unknown session, an invalid id, an after the tree does not hold) are errors before the first yield. The consumer may call the storage from inside the loop; a malformed line ends the stream with ErrCorrupt naming it; a torn tail waits for the writer; and a session deleted — or deleted and created again — under its watcher ends the stream with ErrNotFound.

Types

type RawHeaderInjector

type RawHeaderInjector interface {
	InjectHeader(ctx context.Context, session string, line []byte) error
}

RawHeaderInjector is the optional hook for the header's loud rows: it creates a session whose first line is the given bytes, verbatim (the hook adds the newline) — the header a newer weft, or a broken writer, would have left. Creating over an existing session fails with ErrExists.

type RawInjector

type RawInjector interface {
	Inject(ctx context.Context, session string, data []byte) error
}

RawInjector is the optional hook a durable backend implements so the table can exercise the loud rows the Storage interface itself cannot express: it appends raw bytes to a session's stored data, verbatim — the way a crashed or newer writer would have left them. The test controls the bytes exactly, torn tails included (no trailing newline).

Jump to

Keyboard shortcuts

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