expect

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

README

go-expect

Go 1.26+ conversations with interactive programs and duplex streams: bounded matching, continuations, reusable rules, process terminals and streaming secret redaction. Import github.com/gatework/go-expect.

NewSession takes exclusive ownership of an io.ReadWriteCloser only on success. Its Close must interrupt concurrent reads and writes. Register secrets with Redact before sending or receiving them; matching results retain raw bytes.

The example below is copied from example_test.go, compiled and executed by go test. script/readme.py checks that the copy stays synchronized.

// SPDX-License-Identifier: Apache-2.0

package expect_test

import (
	"context"
	"fmt"
	"io"
	"net"

	expect "github.com/gatework/go-expect"
)

func ExampleSession_Expect() {
	local, remote := net.Pipe()
	session, err := expect.NewSession(local, expect.WithLineEnding("\r"))
	if err != nil {
		panic(err)
	}
	defer session.Close()
	done := make(chan struct{})
	go func() {
		defer close(done)
		defer remote.Close()
		_, _ = remote.Write([]byte("Password:"))
		b := make([]byte, 7)
		_, _ = io.ReadFull(remote, b)
		_, _ = remote.Write([]byte("router#"))
	}()
	if err = session.Redact("secret"); err != nil {
		panic(err)
	}
	result, err := session.Expect(context.Background(), []expect.Case{
		{Pattern: expect.Literal("Password:"), Handler: expect.ReplyThenContinue("secret\r"), Limit: 1},
		{Pattern: expect.Line("router#")},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(result.CaseIndex, result.Match)
	<-done
	// Output: 1 router#
}

Run the longer password, confirmation and paging example with go run ./examples/dialogue. Device authentication, vendor profiles and backup policy belong to go-net-connector, not to the stream engine.

Task API and behavior
Match text Literal, Line, Compile, FromRegexp, Glob, All
Case folding Pattern.Fold; Unicode regexp folding, Go leftmost-first matching
Preview Case.NoConsume; Continue observes each unchanged candidate once
Inspect match Result.Buffer, Capture(name), Indices(group); half-open byte offsets
Repeat Continue resets the relative timer; ContinueKeepTimer preserves it; zero action is Return
Recover timeout Timeout, Default, FullBuffer cases with bounded Case.Limit
Reuse rules WithBefore, WithAfter, per-call WithRules; Session.Rules returns a copy
Reply ReplyThenContinue, or Then(Response) for dynamic replies
Send Send writes once; SendAll drains peer input while writing suffixes
Slow devices SendSlow, SendPaced, bounded SendNull, transport-capability SendBreak
Trace safely WithLogger(*slog.Logger), WithObserver, WithTrace; metadata only
Record streams WithOutput and WithSendOutput; independent redacted streams
Take over Interact, local Literal/Go regexp/Glob actions and input EOF/idle events; InteractTerminal for Unix raw input
Watch several Group.Expect; bound cases, live Add/Remove, fair continuation across members
Watch asynchronously StartBackground; Pause/Resume for foreground takeover; context-bounded Stop/Wait
Normalize input Opt-in WithRemoveNulls(true), WithStripParity(true)

Before rules take precedence over call cases, then After rules. Within a group, the earliest visible byte wins; declaration order breaks ties. Per-call rules follow foreground cases, so completion wins an exact tie. WithMatchOrder(Declaration) selects the first declared matching case within its group. Limits count per wait; rule/event defaults are one invocation, and ordinary cases default to unlimited.

Timeout handles only the relative wait timer; parent cancellation and idle timeouts remain errors. Its callback receives a fresh relative budget, allowing it to send and Continue. Default handles relative timeout or clean EOF. FullBuffer explicitly consumes the full unmatched snapshot; without it overflow returns ErrBufferLimit and preserves data. No automatic trimming occurs.

Use a parent context to bound the whole dialogue. A matching prompt is not proof that a command succeeded. Sends are serialized, but callers must sequence commands and responses. Failures after write admission retire the session and return exact WriteError.Written progress; commands are never retried implicitly.

Interact owns its input reader after admission and closes it on exit. Output is borrowed and filtered. Control-] exits by default. Use WithInteractInputCases for local text patterns and EOF/Timeout cases, WithInteractInputTimeout for local idle timing, and WithInteractOutputCases for remote patterns; callbacks run on the receive owner. InteractTerminal borrows an *os.File, duplicates it, enables raw mode and restores the original terminal state. Interaction retains a matching window of min(64 KiB, buffer limit), consuming half when unmatched (at least one byte), so patterns requiring arbitrary historical prefixes are unsuitable. Stop and interactive cleanup join cooperative callbacks; Session.Close does not wait for user code.

Process adapter Ownership and platform
Console.Start(*exec.Cmd) New session and controlling terminal; caller must cancel and Wait
spawn.Start(ctx, program, args, ...) Unix PTY or Windows 10 1809+ ConPTY, with common environment/directory/status options

spawn.Process.Wait returns (ExitStatus, error); a failed exit is a typed spawn.ExitError. PID and Terminal expose identity and terminal capabilities without exposing the transport. Unix PTYs and Windows ConPTY default to 24 by 80. Unix cancellation sends group SIGHUP with a two-second direct-child kill fallback; descendants must cooperate. Windows uses a kill-on-close job. Consume output before Wait, or Close to interrupt an undrained child. FreeBSD has compile/vet coverage; native runtime coverage is Linux, macOS and Windows.

Session.SetRules atomically replaces Before/After rules at the next scan boundary. Case.Sessions binds Group declarations; an empty selector matches every member. Group controls acknowledge ownership changes, and run outside callbacks. Background Pause releases input after callbacks finish, freezing local budgets while parent deadlines keep running. Resume returns ErrBusy while a foreground receiver owns it.

Relay connects explicit Route{Source, Targets, Output, Cases} declarations. Targets receive raw bytes through bounded queues; operator Output is independently redacted. Matched bytes are withheld unless ForwardMatches is selected. EOF/Return drains queued output; errors cancel and join admitted writes without replay.

Tcl expect convention This library
Declaration order Earliest byte by default; opt into Declaration
10-second timeout 30-second wait, 10-second Send
-1 means unlimited NoTimeout (zero); negatives are invalid
Unhandled timeout returns ErrTimeout
match_max may drop old data Bounded input; overflow preserves it
NUL removal by default Bytes preserved unless filtering is enabled
Tcl ARE expressions Go RE2; no backreferences or lookahead
Typical send terminator CR Send/Reply verbatim, SendLine defaults to LF

See contracts, migration, verification and release operations. The source retains Netflix's Apache-2.0 license and copyright notices.

Run bash script/ci for local checks. CI runs Linux/macOS Go 1.26/1.27 and Windows runtime tests. Default-branch pushes publish after all gates pass. Before v1, BREAKING: or BREAKING CHANGE: commit trailers select the next minor version; ordinary changes increment patch. No compatibility aliases are provided.

Documentation

Overview

Package expect automates interactive byte streams with bounded waits, continuation callbacks and reusable response rules. Session owns a supplied duplex transport; Console adapts Unix PTYs. The spawn and conpty subpackages own child process lifetimes. Callers own connections and command success checks.

Matching preserves input unless explicit normalization options are enabled. Diagnostics and output copies use explicit streaming secret redaction. Result fields are raw, detached snapshots.

Index

Examples

Constants

View Source
const NoTimeout time.Duration = 0

NoTimeout disables a relative timeout; parent context deadlines still apply.

Variables

View Source
var (
	ErrInvalidPattern  = errors.New("expect: invalid pattern")
	ErrClosed          = errors.New("expect: session closed")
	ErrBusy            = errors.New("expect: another expect operation is active")
	ErrTimeout         = errors.New("expect: timeout")
	ErrIdleTimeout     = errors.New("expect: idle timeout")
	ErrBufferLimit     = errors.New("expect: buffer limit reached")
	ErrEmptyMatch      = errors.New("expect: empty match")
	ErrPollUnsupported = errors.New("expect: OS polling unsupported")
)

Sentinel errors describe protocol and lifecycle failures.

View Source
var ErrBreakUnsupported = errors.New("expect: transport break unsupported")

ErrBreakUnsupported indicates that the transport cannot assert a line break.

View Source
var ErrNoSessions = errors.New("expect: no sessions")

ErrNoSessions means a Group has no remaining members to receive from.

Functions

func Relay added in v0.4.0

func Relay(ctx context.Context, routes []Route, opts ...ExpectOption) (err error)

Relay connects sources and targets with one receive owner per source. EOF or Return ends the relay and drains queued output. Failure cancels all routes, joins admitted writes and never replays bytes. Sessions and sinks are borrowed.

Types

type Action

type Action uint8

Action determines whether a matched Case returns or continues waiting.

const (
	Return Action = iota
	Continue
	ContinueKeepTimer
)

Actions determine whether a handler returns or continues waiting.

type Background added in v0.2.0

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

Background owns an asynchronous receive operation. Pause releases its receive reservation after the current callback. Controls must be called outside callbacks. Return, EOF, an error or cancellation completes it; Continue keeps listening.

func (*Background) Pause added in v0.4.0

func (b *Background) Pause(ctx context.Context) error

Pause waits for the current callback, freezes local budgets and releases input. Parent context deadlines continue to run while paused. Repeated Pause is harmless.

func (*Background) Resume added in v0.4.0

func (b *Background) Resume(ctx context.Context) error

Resume reacquires input or returns ErrBusy, leaving the background paused.

func (*Background) Stop added in v0.2.0

func (b *Background) Stop(ctx context.Context) error

Stop cancels the operation and waits for cooperative callbacks and cleanup.

func (*Background) Wait added in v0.2.0

func (b *Background) Wait(ctx context.Context) (Result, error)

Wait joins the operation. Canceling ctx bounds the join, not the operation.

type CallbackError added in v0.4.0

type CallbackError struct{ Kind CallbackFailure }

CallbackError reports abnormal callback completion without retaining payloads.

func (*CallbackError) Error added in v0.4.0

func (e *CallbackError) Error() string

func (*CallbackError) GoString added in v0.4.0

func (e *CallbackError) GoString() string

GoString formats callback failure metadata without a panic value.

type CallbackFailure added in v0.4.0

type CallbackFailure string

CallbackFailure identifies how an asynchronous callback failed.

const (
	// CallbackPanicked means a callback panicked; its payload is deliberately omitted.
	CallbackPanicked CallbackFailure = "panic"
	// CallbackExited means a callback terminated its goroutine without returning.
	CallbackExited CallbackFailure = "goexit"
)

type Capture

type Capture struct {
	Text       string
	Matched    bool
	Name       string
	Start, End int
}

Capture distinguishes an unmatched optional group from an empty matched group.

func (Capture) GoString

func (c Capture) GoString() string

GoString formats metadata without exposing payloads.

func (Capture) String

func (Capture) String() string

type Case

type Case struct {
	// NoConsume preserves matched input. Continue suppresses this candidate until the buffer changes.
	NoConsume bool
	// Sessions binds a Group case to members; an empty slice selects every member.
	Sessions []*Session
	Pattern  Pattern
	Handler  Handler
	Limit    int
}

Case associates a pattern with an optional handler (nil means Return).

func (Case) GoString

func (c Case) GoString() string

GoString formats metadata without exposing payloads.

func (Case) String

func (Case) String() string

type CaseLimitError added in v0.4.0

type CaseLimitError struct {
	SessionID            SessionID
	Source               MatchSource
	Scope                RuleScope
	CaseIndex, RuleIndex int
}

CaseLimitError identifies a response rule that exceeded its per-call limit.

func (*CaseLimitError) Error added in v0.4.0

func (e *CaseLimitError) Error() string

func (*CaseLimitError) GoString added in v0.4.0

func (e *CaseLimitError) GoString() string

GoString formats case-limit metadata without exposing user fields.

type CompileError added in v0.2.0

type CompileError struct{ Code syntax.ErrorCode }

CompileError retains the syntax category, never the original expression.

func (*CompileError) Error added in v0.2.0

func (e *CompileError) Error() string

func (*CompileError) Unwrap added in v0.3.0

func (e *CompileError) Unwrap() error

Unwrap identifies a rejected pattern without revealing its expression.

type ConfigError added in v0.2.0

type ConfigError struct {
	Option string
	Reason string
}

ConfigError identifies a rejected option without retaining caller payloads.

func (*ConfigError) Error added in v0.2.0

func (e *ConfigError) Error() string

type Console

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

Console owns a PTY and Session. Start attaches a caller-owned process to a controlling terminal; the caller remains responsible for cancellation and Wait.

func NewConsole

func NewConsole(opts ...ConsoleOption) (*Console, error)

NewConsole creates a PTY and a Session. Failure closes every acquired handle.

func (*Console) Close

func (c *Console) Close() error

Close closes the Session and both PTY handles, not any attached process.

func (*Console) GoString added in v0.2.0

func (p *Console) GoString() string

GoString formats the handle without exposing owned stream or command state.

func (*Console) SendBreak added in v0.2.0

func (c *Console) SendBreak(ctx context.Context, duration time.Duration) error

SendBreak asserts a terminal break through the serialized Session write path.

func (*Console) Session

func (c *Console) Session() *Session

Session returns the single owner of Console's matching and sending operations.

func (*Console) SetEcho added in v0.2.0

func (c *Console) SetEcho(enabled bool) error

SetEcho changes ECHO without changing the terminal's other flags.

func (*Console) SetSize added in v0.2.0

func (c *Console) SetSize(rows, cols int) error

SetSize changes the terminal dimensions, notifying its foreground process group.

func (*Console) Start added in v0.2.0

func (c *Console) Start(cmd *exec.Cmd) error

Start attaches cmd to the PTY as a new session with a controlling terminal. The caller owns cancellation and Wait. On success the parent's slave is closed.

func (*Console) String added in v0.2.0

func (*Console) String() string

String formats the handle without exposing owned stream or command state.

func (*Console) TTY added in v0.1.1

func (c *Console) TTY() *os.File

TTY returns the slave; the master is intentionally not exposed for competing reads. When attaching manually, release the parent slave after start to observe EOF. Console.Start performs that release automatically.

type ConsoleOption

type ConsoleOption func(*consoleOptions) error

ConsoleOption configures the PTY adapter.

func WithRaw

func WithRaw() ConsoleOption

WithRaw configures the slave in raw mode, including disabling echo.

func WithSessionOptions

func WithSessionOptions(opts ...SessionOption) ConsoleOption

WithSessionOptions configures the Console's underlying Session.

func WithSize added in v0.2.0

func WithSize(rows, cols int) ConsoleOption

WithSize sets the initial terminal dimensions. The default is 24 rows by 80 columns.

type ErrClass added in v0.2.0

type ErrClass string

ErrClass is a payload-free error classification.

const (
	ClassOK          ErrClass = ""
	ClassCanceled    ErrClass = "canceled"
	ClassDeadline    ErrClass = "deadline"
	ClassClosed      ErrClass = "closed"
	ClassBusy        ErrClass = "busy"
	ClassTimeout     ErrClass = "timeout"
	ClassIdleTimeout ErrClass = "idle_timeout"
	ClassEOF         ErrClass = "eof"
	ClassError       ErrClass = "error"
)

Error classes exposed by metadata diagnostics.

func (ErrClass) String added in v0.2.0

func (c ErrClass) String() string

type Event

type Event struct {
	SessionID        SessionID
	Source           MatchSource
	Scope            RuleScope
	RuleIndex        int
	Operation        Op
	CaseIndex, Bytes int
	Error            ErrClass
}

Event contains metadata only. Error is a classification, never a raw message.

type ExpectOption

type ExpectOption func(*expectOptions) error

ExpectOption configures matching or timing for a single Expect operation.

func WithIdleTimeout

func WithIdleTimeout(d time.Duration) ExpectOption

WithIdleTimeout limits time without new input; zero disables it.

func WithMatchOrder added in v0.2.0

func WithMatchOrder(order MatchOrder) ExpectOption

WithMatchOrder selects earliest-byte or declaration-order matching.

func WithRules added in v0.2.0

func WithRules(cases ...Case) ExpectOption

WithRules activates reusable cases alongside foreground cases. Foreground cases win equal-position ties. A zero rule limit means one response per wait.

func WithStreamingFallback added in v0.1.1

func WithStreamingFallback(handler Handler) ExpectOption

WithStreamingFallback consumes max(1, H/2) bytes when H bytes are pending, where H is min(64 KiB, the session buffer limit), and no ordinary case matches. Result.CaseIndex is -1 and Match is the consumed data. Continue and ContinueKeepTimer both preserve all existing timers.

func WithTimeout

func WithTimeout(d time.Duration) ExpectOption

WithTimeout sets the relative timer, including elapsed callback time. Zero disables it. Continue can reset it; context deadlines cannot be reset.

type Group added in v0.4.0

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

Group is a mutable set of borrowed Sessions. Only one Expect may run on a Group. Membership controls run outside its callbacks and acknowledge input ownership changes before returning. Group never closes its member transports.

func NewGroup added in v0.4.0

func NewGroup(sessions ...*Session) (*Group, error)

NewGroup validates a borrowed member set; it performs no transport I/O.

func (*Group) Add added in v0.4.0

func (g *Group) Add(ctx context.Context, s *Session) error

Add installs a member; during Expect its receive reservation is acquired first.

func (*Group) Expect added in v0.4.0

func (g *Group) Expect(ctx context.Context, cases []Case, opts ...ExpectOption) (*Session, Result, error)

Expect keeps every active member eligible across continuations. Before/call/ After groups apply across members, with round-robin ties. Case.Sessions binds a declaration; an empty selector matches all members. Return completes the wait.

func (*Group) Remove added in v0.4.0

func (g *Group) Remove(ctx context.Context, s *Session) error

Remove releases a member's reservation and preserves its unconsumed input.

type Handler

type Handler func(context.Context, *Session, Result) (Action, error)

Handler runs after consumption, outside internal locks. It may send or close, but may not call Expect recursively on the same Session.

func ReplyThenContinue added in v0.2.0

func ReplyThenContinue(text string) Handler

ReplyThenContinue sends text verbatim and continues without resetting timers.

func Then added in v0.2.0

func Then(response Response) Handler

Then adapts a response to a timer-preserving continuation handler.

type InteractOption added in v0.2.0

type InteractOption func(*interactOptions) error

InteractOption configures an owned interactive receive operation.

func WithEscape added in v0.2.0

func WithEscape(key byte) InteractOption

WithEscape sets the local exit byte; the default is control-] (0x1d).

func WithInteractCookedCallbacks added in v0.4.0

func WithInteractCookedCallbacks() InteractOption

WithInteractCookedCallbacks restores the borrowed terminal during callbacks. It requires InteractTerminal; ordinary Interact rejects the option.

func WithInteractEcho added in v0.4.0

func WithInteractEcho(enabled bool) InteractOption

WithInteractEcho echoes matched local sequences to the redacted operator output.

func WithInteractExpectOptions added in v0.2.0

func WithInteractExpectOptions(opts ...ExpectOption) InteractOption

WithInteractExpectOptions configures matching and timing during interaction.

func WithInteractInputCases added in v0.3.0

func WithInteractInputCases(cases ...Case) InteractOption

WithInteractInputCases installs ordered local text patterns and input events. Statically empty text matches and NoConsume are rejected before admission.

func WithInteractInputForward added in v0.4.0

func WithInteractInputForward(enabled bool) InteractOption

WithInteractInputForward forwards matched local sequences before the callback.

func WithInteractInputTimeout added in v0.4.0

func WithInteractInputTimeout(d time.Duration) InteractOption

WithInteractInputTimeout sets the local idle interval. Timeout/Default input cases receive this event; without a case expiry returns ErrIdleTimeout.

func WithInteractOutputCases added in v0.3.0

func WithInteractOutputCases(cases ...Case) InteractOption

WithInteractOutputCases adds remote pattern actions; Return ends interaction.

func WithInteractOutputForward added in v0.4.0

func WithInteractOutputForward(enabled bool) InteractOption

WithInteractOutputForward selects immediate remote passthrough (the default). Disabling it withholds matched bytes; unmatched bytes use the bounded window.

type MatchOrder added in v0.2.0

type MatchOrder uint8

MatchOrder chooses text-case arbitration within a rule group.

const (
	Earliest MatchOrder = iota
	Declaration
)

Match orders select earliest-byte or declaration-first arbitration.

type MatchSource added in v0.3.0

type MatchSource uint8

MatchSource identifies the byte stream evaluated by a match.

const (
	SourceReceive MatchSource = iota
	SourceInput
)

Sources distinguish remote receive data from local interactive input.

func (MatchSource) String added in v0.3.0

func (s MatchSource) String() string

type Observer

type Observer func(Event)

Observer is invoked once per completed public Expect, Send or SendAll operation. Observers run synchronously outside locks and must not block indefinitely.

type Op added in v0.2.0

type Op string

Op identifies an observed operation.

const (
	OpExpect   Op = "expect"
	OpInteract Op = "interact"
	OpSend     Op = "send"
	OpSendAll  Op = "send_all"
)

Operation names emitted by completion observers.

func (Op) String added in v0.2.0

func (o Op) String() string

type Pacer added in v0.4.0

type Pacer func(offset int) time.Duration

Pacer returns the delay before the byte at offset. It runs under send serialization and must return a nonnegative duration without blocking.

type Pattern

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

Pattern is an immutable literal, regular expression, EOF or All pattern. Its zero value is invalid. It never contains per-operation match state.

func All

func All(patterns ...Pattern) Pattern

All matches all supplied text patterns, in any order, consuming through the last match. Repeated patterns may refer to the same bytes. Captures are omitted.

func Compile

func Compile(expr string) (Pattern, error)

Compile compiles a Go regular expression. Empty matches are rejected at runtime.

func Default added in v0.2.0

func Default() Pattern

Default matches a relative timeout or clean EOF when selected in declaration order.

func EOF

func EOF() Pattern

EOF matches a clean end of input, after buffered text has been matched.

func FromRegexp added in v0.1.1

func FromRegexp(re *regexp.Regexp) (Pattern, error)

FromRegexp snapshots a compiled expression, including POSIX/Longest semantics. Later changes to the caller's expression do not affect the Pattern.

func FullBuffer added in v0.2.0

func FullBuffer() Pattern

FullBuffer explicitly accepts a full unmatched buffer and consumes that snapshot.

func Glob added in v0.2.0

func Glob(pattern string) Pattern

Glob matches unanchored Tcl-style Unicode glob text. Stars cross newlines. Invalid glob declarations fail pattern validation. Literal/Line support raw bytes.

func Line added in v0.1.3

func Line(text string) Pattern

Line matches a byte-exact final line with optional space, tab and NUL padding. The match includes its preceding CR/LF delimiter and trailing CR/LF bytes. Empty text and text containing CR or LF are invalid declarations.

func Literal

func Literal(text string) Pattern

Literal matches a nonempty byte string, including arbitrary binary data.

func Timeout added in v0.2.0

func Timeout() Pattern

Timeout matches the relative wait deadline, never parent cancellation.

func (Pattern) Fold added in v0.2.0

func (p Pattern) Fold() Pattern

Fold returns an isolated case-insensitive text pattern. Unicode regexp folding applies to Literal and regexp patterns; Line keeps its final-line anchoring. Fold selects Go leftmost-first mode, including for imported POSIX/Longest regexps.

func (Pattern) GoString

func (p Pattern) GoString() string

GoString formats metadata without exposing payloads.

func (Pattern) IsZero added in v0.1.3

func (p Pattern) IsZero() bool

IsZero distinguishes an unspecified pattern from a malformed declaration.

func (Pattern) String

func (p Pattern) String() string

func (Pattern) ValidateText added in v0.1.3

func (p Pattern) ValidateText() error

ValidateText checks a nonempty text declaration before transport access. EOF and regular expressions matching empty input are not text declarations.

type Redactor added in v0.1.1

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

Redactor filters one diagnostic stream independently of any Session. Rules, retained prefixes and replacement runs are scoped to this instance. Methods are concurrency safe. Formatting never exposes secrets or retained data.

func NewRedactor added in v0.1.1

func NewRedactor(secrets []string, replacement string) (*Redactor, error)

NewRedactor copies literal secrets and the nonempty custom replacement.

func (*Redactor) Append added in v0.1.1

func (r *Redactor) Append(data []byte) []byte

Append returns only bytes that cannot begin a future secret match. It replaces each contiguous union of overlapping or adjacent matches once, across chunks.

func (*Redactor) Finish added in v0.1.1

func (r *Redactor) Finish() []byte

Finish ends a stream and conservatively masks any retained secret prefix. Repeated calls are empty; the instance may then filter a fresh stream.

func (*Redactor) GoString added in v0.1.1

func (r *Redactor) GoString() string

GoString formats metadata without exposing payloads.

func (*Redactor) SetSecrets added in v0.1.1

func (r *Redactor) SetSecrets(secrets []string) ([]byte, error)

SetSecrets ends the old rule scope and returns its safe final bytes. Suspected old secret prefixes are masked before new rules take effect. Invalid rules leave the existing scope untouched. Callers must deliver the returned bytes.

func (*Redactor) String added in v0.1.1

func (*Redactor) String() string

type Response

type Response func(context.Context, *Session, Result) error

Response is a borrowed side-effect callback adapted by Then.

func Reply

func Reply(text string) Response

Reply returns an action that sends text verbatim, including explicit line ends.

type Result

type Result struct {
	SessionID     SessionID
	Source        MatchSource
	CaseIndex     int
	RuleIndex     int
	Scope         RuleScope
	Start, End    int
	Before, Match string
	Captures      []Capture
}

Result is a detached snapshot of the final match, not the whole conversation. Before contains pending input on failure; CaseIndex is -1 without a match. Explicit fields contain unredacted bytes; formatting emits only metadata.

func (Result) Buffer added in v0.2.0

func (r Result) Buffer() string

Buffer returns the raw consumed segment (or the no-consume snapshot).

func (Result) Capture added in v0.2.0

func (r Result) Capture(name string) (Capture, bool)

Capture returns the first matched named group.

func (Result) GoString

func (r Result) GoString() string

GoString formats metadata without exposing payloads.

func (Result) Indices added in v0.2.0

func (r Result) Indices(group int) (int, int, bool)

Indices returns half-open byte offsets within the buffer visible to this match. Group zero is the entire match; higher groups index Captures from one.

func (Result) String

func (r Result) String() string

type Route added in v0.4.0

type Route struct {
	Source         *Session
	Targets        []*Session
	Output         io.Writer
	Cases          []Case
	ForwardMatches bool
	QueueLimit     int
}

Route forwards unmatched bytes from one borrowed Session. Matched bytes are consumed unless ForwardMatches is set. Output is a borrowed, redacted operator stream; Targets receive raw protocol bytes. A source occurs in only one route. QueueLimit bounds queued plus in-flight bytes per target (default: source limit).

type RouteError added in v0.4.0

type RouteError struct {
	Source, Target SessionID
	Written        int
	Cause          error
}

RouteError preserves the total accepted progress of a failing target. Error formats metadata only; Unwrap explicitly exposes the underlying cause.

func (*RouteError) Error added in v0.4.0

func (e *RouteError) Error() string

func (*RouteError) GoString added in v0.4.0

func (e *RouteError) GoString() string

GoString omits the underlying transport error.

func (*RouteError) Unwrap added in v0.4.0

func (e *RouteError) Unwrap() error

type RuleScope added in v0.2.0

type RuleScope string

RuleScope identifies a persistent or call-scoped rule group.

const (
	ScopeBefore RuleScope = "before"
	ScopeCall   RuleScope = "call"
	ScopeAfter  RuleScope = "after"
)

Scopes identify the persistent or per-call rule group.

type RuleSnapshot added in v0.2.0

type RuleSnapshot struct{ Before, After []Case }

RuleSnapshot is a detached declaration snapshot; callback closures remain borrowed.

type Session

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

Session owns one duplex transport and one matching buffer. The transport must support concurrent Read/Write and guarantee Close interrupts both. After a successful NewSession, callers must not perform I/O on that transport. Expect and Poll are exclusive. Sends are serialized; Close and Redact are concurrency safe. SendAll may queue bounded input for the next Expect. Do not copy a Session. Callback and output execution is cooperative.

func NewSession

func NewSession(transport io.ReadWriteCloser, opts ...SessionOption) (*Session, error)

NewSession transfers transport ownership only on success. Options are validated before any reads, writes or closes take place.

func (*Session) Close

func (s *Session) Close() error

Close interrupts I/O and joins owned workers. It never waits for application callbacks or closes borrowed output writers. Repeated calls return the same error.

func (*Session) Expect

func (s *Session) Expect(ctx context.Context, cases []Case, opts ...ExpectOption) (Result, error)

Expect consumes input through the earliest currently visible match. Ties use declaration order. Continuations consume their match and rescan the remaining buffer, without rereading or replaying bytes. Context cancellation and timeout leave unmatched input available to a subsequent call. Callers must not change the foreground cases until the call returns.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"net"

	expect "github.com/gatework/go-expect"
)

func main() {
	local, remote := net.Pipe()
	session, err := expect.NewSession(local, expect.WithLineEnding("\r"))
	if err != nil {
		panic(err)
	}
	defer session.Close()
	done := make(chan struct{})
	go func() {
		defer close(done)
		defer remote.Close()
		_, _ = remote.Write([]byte("Password:"))
		b := make([]byte, 7)
		_, _ = io.ReadFull(remote, b)
		_, _ = remote.Write([]byte("router#"))
	}()
	if err = session.Redact("secret"); err != nil {
		panic(err)
	}
	result, err := session.Expect(context.Background(), []expect.Case{
		{Pattern: expect.Literal("Password:"), Handler: expect.ReplyThenContinue("secret\r"), Limit: 1},
		{Pattern: expect.Line("router#")},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(result.CaseIndex, result.Match)
	<-done
}
Output:
1 router#

func (*Session) GoString added in v0.2.0

func (p *Session) GoString() string

GoString formats the handle without exposing owned stream or command state.

func (*Session) ID added in v0.4.0

func (s *Session) ID() SessionID

ID returns this Session's immutable identity.

func (*Session) Interact added in v0.2.0

func (s *Session) Interact(ctx context.Context, input io.ReadCloser, output io.Writer, opts ...InteractOption) (err error)

Interact relays through the sole Session receiver. It owns input on successful admission; input.Close must interrupt Read. Output is borrowed and redacted. Escape/EOF cancels and joins the input worker. In-flight write cancellation retains the ordinary Send retirement contract. Remote match tails are bounded by a streaming window of min(64 KiB, buffer limit), not arbitrary regexp prefixes.

func (*Session) InteractTerminal added in v0.2.0

func (s *Session) InteractTerminal(ctx context.Context, input *os.File, output io.Writer, opts ...InteractOption) (err error)

InteractTerminal temporarily puts a caller-owned local terminal in raw mode. Input is duplicated for cancellation and the original handle remains open. The caller must not read input or change its terminal settings during the call.

func (*Session) Poll added in v0.1.1

func (s *Session) Poll(ctx context.Context, cases []Case, opts ...ExpectOption) (Result, error)

Poll matches buffered bytes and performs one OS readiness pass without waiting for future input. No match returns ErrTimeout. A zero Expect timeout instead disables that timer and continues waiting. Requires WithOSPolling.

func (*Session) Redact

func (s *Session) Redact(secrets ...string) error

Redact adds nonempty literal secrets before sending or receiving them. Registration cannot retract bytes already delivered to output writers.

func (*Session) Rules added in v0.2.0

func (s *Session) Rules() RuleSnapshot

Rules returns the configured persistent declarations without exposing storage.

func (*Session) Send

func (s *Session) Send(ctx context.Context, text string) (n int, err error)

Send writes text once. No partial write is retried. A failure after entering transport.Write closes the Session and returns its progress in WriteError. An invalid transport count is rejected and reported as zero.

func (*Session) SendAll added in v0.1.1

func (s *Session) SendAll(ctx context.Context, text string) (int, error)

SendAll drains peer input while writing positive-progress unsent suffixes. Its context owns the complete deadline, including serialization; WithWriteTimeout applies only to Send. Receive data is queued up to WithBufferLimit while the peer writes before reading. Exceeding that bound fails instead of deadlocking. Every error after transport entry closes the Session and reports total progress.

func (*Session) SendAllPoll added in v0.1.1

func (s *Session) SendAllPoll(ctx context.Context, text string) (int, error)

SendAllPoll uses immediate OS write readiness for every suffix, permitting an initial write without waiting. A blocked suffix returns ErrTimeout with total progress and closes the Session. Requires WithOSPolling.

func (*Session) SendBreak added in v0.2.0

func (s *Session) SendBreak(ctx context.Context, duration time.Duration) error

SendBreak serializes a transport-specific line break with other writes.

func (*Session) SendLine

func (s *Session) SendLine(ctx context.Context, text string) (int, error)

SendLine appends the configured line ending (LF by default).

func (*Session) SendNull added in v0.2.0

func (s *Session) SendNull(ctx context.Context, count int) (n int, err error)

SendNull sends count NUL bytes in bounded chunks under one send reservation. WithWriteTimeout bounds the whole operation, including admission.

func (*Session) SendPaced added in v0.4.0

func (s *Session) SendPaced(ctx context.Context, text string, pacer Pacer) (n int, err error)

SendPaced serializes an entire user-defined pacing policy. No admitted partial write is retried; cancellation after any progress retires the Session.

func (*Session) SendSlow added in v0.2.0

func (s *Session) SendSlow(ctx context.Context, text string, chunk int, interval time.Duration) (n int, err error)

SendSlow writes byte chunks under one cancellable send lock. The interval is between chunks; it never splits a write retry. Context bounds the whole send.

func (*Session) SetRules added in v0.4.0

func (s *Session) SetRules(r RuleSnapshot) error

SetRules atomically replaces persistent Before/After declarations. Active waits observe the new snapshot after the current callback; timers are preserved.

func (*Session) StartBackground added in v0.2.0

func (s *Session) StartBackground(ctx context.Context, cases []Case, opts ...ExpectOption) (*Background, error)

StartBackground reserves the receiver synchronously. Relative timing defaults to NoTimeout; explicit options override it. Callback panic/Goexit are reported by Wait as CallbackError without retaining the panic value.

func (*Session) String added in v0.2.0

func (*Session) String() string

String formats the handle without exposing owned stream or command state.

type SessionError added in v0.4.0

type SessionError struct {
	SessionID SessionID
	Cause     error
}

SessionError associates a failed member with its immutable identity. Formatting omits transport errors; Unwrap explicitly exposes the cause.

func (*SessionError) Error added in v0.4.0

func (e *SessionError) Error() string

func (*SessionError) GoString added in v0.4.0

func (e *SessionError) GoString() string

GoString omits the underlying transport error.

func (*SessionError) Unwrap added in v0.4.0

func (e *SessionError) Unwrap() error

type SessionID added in v0.4.0

type SessionID uint64

SessionID is a process-local identity that remains stable across Group changes.

type SessionOption

type SessionOption func(*sessionOptions) error

SessionOption configures a Session before it takes ownership of the transport.

func WithAfter added in v0.2.0

func WithAfter(cases ...Case) SessionOption

WithAfter installs session rules consulted after call cases.

func WithBefore added in v0.2.0

func WithBefore(cases ...Case) SessionOption

WithBefore installs session rules that precede call cases.

func WithBufferLimit

func WithBufferLimit(n int) SessionOption

WithBufferLimit bounds unconsumed matching input; overflow never discards data.

func WithDefaultTimeout

func WithDefaultTimeout(d time.Duration) SessionOption

WithDefaultTimeout sets the relative Expect timeout; zero disables it.

func WithLineEnding

func WithLineEnding(s string) SessionOption

WithLineEnding selects LF, CR or CRLF for SendLine. Send and Reply are verbatim.

func WithLogger

func WithLogger(logger *slog.Logger) SessionOption

WithLogger enables metadata-only diagnostics; payloads and causes are omitted.

func WithOSPolling added in v0.1.1

func WithOSPolling() SessionOption

WithOSPolling enables sole-reader OS readiness coordination for Poll and SendAllPoll. The transport must expose syscall.Conn and a nonblocking OS fd. Unsupported transports fail construction before ownership transfers. A working SetReadDeadline(time.Time) error capability selects the runtime poller; otherwise construction selects the bounded native-poll fallback. Wrappers may forward NormalizeReadError(error) error for transport-specific read errors, such as PTY EIO at EOF. Control errors are never normalized.

func WithObserver

func WithObserver(observers ...Observer) SessionOption

WithObserver adds metadata observers in declaration order.

func WithOutput

func WithOutput(writers ...io.Writer) SessionOption

WithOutput copies received input through the streaming secret filter. Writers are borrowed, synchronous and must not reenter Session methods. Failure closes the Session; delivery to earlier writers is not replayed.

func WithRemoveNulls added in v0.2.0

func WithRemoveNulls(enabled bool) SessionOption

WithRemoveNulls explicitly removes NUL bytes from the matching/receive view. The default preserves every transport byte.

func WithSendOutput added in v0.2.0

func WithSendOutput(writers ...io.Writer) SessionOption

WithSendOutput records actual transport-accepted bytes through a separate streaming secret filter. Writers are borrowed, synchronous and never retried.

func WithStripParity added in v0.2.0

func WithStripParity(enabled bool) SessionOption

WithStripParity explicitly clears the high bit before optional NUL removal.

func WithTrace added in v0.2.0

func WithTrace(observers ...TraceObserver) SessionOption

WithTrace enables payload-free candidate diagnostics, independently of completion observers.

func WithWriteTimeout

func WithWriteTimeout(d time.Duration) SessionOption

WithWriteTimeout bounds each Send, including serialization wait; zero disables it.

type TraceEvent added in v0.2.0

type TraceEvent struct {
	SessionID                       SessionID
	Source                          MatchSource
	Scope                           RuleScope
	RuleIndex                       int
	CaseIndex, Buffered, Start, End int
	Pattern                         string
	Matched                         bool
}

TraceEvent describes a candidate evaluation without retaining payloads or causes.

type TraceObserver added in v0.2.0

type TraceObserver func(TraceEvent)

TraceObserver runs synchronously outside locks; it must not reenter Session.

type WriteError

type WriteError struct {
	Written int
	Cause   error
}

WriteError reports a failed, non-retryable write. Error never prints payloads or the underlying error message; Unwrap exposes the original cause explicitly.

func (*WriteError) Error

func (e *WriteError) Error() string

func (*WriteError) GoString

func (e *WriteError) GoString() string

GoString formats metadata without exposing payloads.

func (*WriteError) Unwrap

func (e *WriteError) Unwrap() error

Directories

Path Synopsis
examples
dialogue command
Command dialogue runs a local simulated device; no network connection is made.
Command dialogue runs a local simulated device; no network connection is made.
Package expecttest provides test-owned PTY consoles without logging goroutines.
Package expecttest provides test-owned PTY consoles without logging goroutines.
internal
redact
Package redact owns incremental coverage and bounded streaming storage.
Package redact owns incremental coverage and bounded streaming storage.
Package spawn owns interactive child processes through PTYs on Unix and pseudoconsoles on Windows.
Package spawn owns interactive child processes through PTYs on Unix and pseudoconsoles on Windows.

Jump to

Keyboard shortcuts

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