fakepool

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package fakepool provides a deterministic in-process replacement for nntppool.Client used by tests that exercise AltMount's streaming and connection-management invariants.

The real nntppool.Client opens TCP sockets, dials providers, and runs background reconnect / quota / pipelining goroutines. None of that is useful when the question under test is "does the streaming pipeline cap in-flight downloads", "does a slow provider cause a retry storm", or "do ephemeral readers leak goroutines". For those questions we need a client we can drive moment-by-moment: control latency, inject specific errors, count concurrent calls, and observe whether the caller honors the backpressure we apply.

Client satisfies pool.NntpClient (see internal/pool/nntpclient.go). The production code never sees a difference; tests inject *Client directly via a pool getter closure that returns it as pool.NntpClient.

Observability primitives

Every Body / BodyPriority / BodyAsync / Stat call increments InFlight on entry and decrements on exit. MaxInFlight records the high-water mark. Tests assert against these counters to pin invariants like "no more than N segment downloads are ever in flight".

Failure injection

SegmentBehavior controls per-message-ID latency, byte payload, and error. Set via SetBehavior or SetDefaultBehavior. Behaviors are evaluated at the start of each call; later changes apply only to subsequent calls.

Backpressure simulation

BlockUntil pins every in-flight call at the entry semaphore until the caller closes the returned channel. Use it to hold connections "open" while observing how the rest of the pipeline reacts.

All public methods are safe for concurrent use.

Index

Constants

This section is empty.

Variables

View Source
var ErrSimulated502 = errors.New("fakepool: simulated 502 service unavailable")

ErrSimulated502 is a generic transient error suitable for retry-storm scenarios. It is wrapped so errors.Is(err, ErrSimulated502) works.

Functions

func AssertMaxInFlightLE

func AssertMaxInFlightLE(tb interface {
	Helper()
	Errorf(format string, args ...any)
}, c *Client, n int32)

AssertMaxInFlightLE fails the test if the high-water mark exceeds n. Use this as the standard assertion at the end of any test that pins a concurrency cap.

Types

type Client

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

Client is a fake nntppool.Client suitable for unit and concurrency tests.

func New

func New() *Client

New returns a fake client. Without further configuration it returns an empty (zero-byte) ArticleBody immediately for every message-ID.

func (*Client) BlockUntil

func (c *Client) BlockUntil(release <-chan struct{})

BlockUntil installs a gate that pins every subsequent call inside the fake (after counter increment, before doing any work) until release is closed. Useful for asserting "exactly N calls are concurrently in flight while the gate is closed".

Pass nil to remove the gate. Setting a new gate replaces any prior one; previously gated calls observing the old gate continue to wait on it.

func (*Client) Body

func (c *Client) Body(ctx context.Context, messageID string, onMeta ...func(nntppool.YEncMeta)) (*nntppool.ArticleBody, error)

Body satisfies pool.NntpClient. Returns either the configured error or an ArticleBody filled with the configured Bytes after the configured latency.

func (*Client) BodyAsync

func (c *Client) BodyAsync(ctx context.Context, messageID string, w io.Writer, onMeta ...func(nntppool.YEncMeta)) <-chan nntppool.BodyResult

BodyAsync streams the configured Bytes (or error) to w and yields a BodyResult on the returned channel.

func (*Client) BodyAsyncCalls

func (c *Client) BodyAsyncCalls() int64

BodyAsyncCalls returns the count of BodyAsync invocations.

func (*Client) BodyCalls

func (c *Client) BodyCalls() int64

BodyCalls returns the count of Body invocations.

func (*Client) BodyPriority

func (c *Client) BodyPriority(ctx context.Context, messageID string, onMeta ...func(nntppool.YEncMeta)) (*nntppool.ArticleBody, error)

BodyPriority is identical to Body but counted separately so tests can distinguish streaming from importer traffic.

func (*Client) BodyPriorityCalls

func (c *Client) BodyPriorityCalls() int64

BodyPriorityCalls returns the count of BodyPriority invocations. This is the most useful counter for streaming-path tests since UsenetReader uses BodyPriority exclusively.

func (*Client) InFlight

func (c *Client) InFlight() int32

InFlight returns the number of calls currently inside the fake.

func (*Client) MaxInFlight

func (c *Client) MaxInFlight() int32

MaxInFlight returns the high-water mark of InFlight observed since the client was created (or since the last ResetCounters).

func (*Client) PerMessageCalls

func (c *Client) PerMessageCalls(messageID string) int64

PerMessageCalls returns how many times the given message-ID was requested across all method types (Body / BodyPriority / BodyAsync / Stat).

This is the primary signal for retry-storm tests: if a retry policy is re-issuing failed requests, the per-ID count climbs faster than the number of distinct segments and the assertion fails.

func (*Client) ResetCounters

func (c *Client) ResetCounters()

ResetCounters zeroes all observability counters. Behaviors and gates are preserved. Useful between phases of a single test.

func (*Client) SetBehavior

func (c *Client) SetBehavior(messageID string, b SegmentBehavior)

SetBehavior sets a per-message-ID behavior. Overrides SetDefaultBehavior.

func (*Client) SetDefaultBehavior

func (c *Client) SetDefaultBehavior(b SegmentBehavior)

SetDefaultBehavior sets the behavior used for any message-ID that has no per-segment override.

func (*Client) SetStats

func (c *Client) SetStats(s nntppool.ClientStats)

SetStats overrides what Stats() returns. Tests that exercise the metrics tracker can use this to feed deterministic provider data.

func (*Client) Stat

func (c *Client) Stat(ctx context.Context, messageID string) (*nntppool.StatResult, error)

Stat returns a StatResult with the message-ID echoed, after the configured latency. If the behavior has Err set, it is returned.

func (*Client) StatCalls

func (c *Client) StatCalls() int64

StatCalls returns the count of Stat invocations.

func (*Client) StatMany

func (c *Client) StatMany(ctx context.Context, messageIDs []string, opts nntppool.StatManyOptions) <-chan nntppool.StatManyResult

StatMany satisfies pool.NntpClient by fanning Stat out across up to opts.Concurrency goroutines, mirroring nntppool's StatMany semantics: results stream out of order, the channel closes once every dispatched check reports, and ctx cancellation stops dispatch and lets in-flight sends bail out.

func (*Client) Stats

func (c *Client) Stats() nntppool.ClientStats

Stats returns the configured stats or a zero value.

func (*Client) TotalCalls

func (c *Client) TotalCalls() int64

TotalCalls returns the total number of method invocations served.

type SegmentBehavior

type SegmentBehavior struct {
	// Latency is the wall-clock delay added before the call returns.
	// Honors ctx cancellation: if the context fires first, the call
	// returns ctx.Err() and does not pretend to have produced data.
	Latency time.Duration

	// Bytes is the payload returned in ArticleBody.Bytes. Length is also
	// used for BytesDecoded.
	Bytes []byte

	// Err, if non-nil, is returned instead of a body. Use nntppool sentinel
	// errors (e.g. nntppool.ErrArticleNotFound) to exercise specific paths
	// in the retry/dispatch logic.
	Err error

	// YEnc, if non-zero, is populated on ArticleBody.YEnc and fired via any
	// onMeta callbacks. Use this to inject yEnc header metadata (FileName,
	// PartSize, FileSize, etc.) without needing a real NNTP server.
	YEnc nntppool.YEncMeta

	// FailFirst makes the first N calls to this message-ID return FailErr (a
	// transient error) before subsequent calls succeed normally. Use it to
	// exercise retry logic that must distinguish transient failures from a
	// permanent article-not-found. Zero disables this behavior.
	FailFirst int

	// FailErr is the transient error returned during the FailFirst window.
	// Defaults to a generic "all providers exhausted"-style error when nil.
	FailErr error
}

SegmentBehavior describes how the fake should respond to a single message-ID. The zero value returns an empty body with no delay.

Jump to

Keyboard shortcuts

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