Documentation
¶
Overview ¶
Package testsupport holds the test doubles and measurement helpers the SDK's tests share: servers, listeners, dialers and a proxy for transport tests (LoopbackServer, SilentListener, RefusedAddr, GatedDialer, FakeH2CServer, Proxy); request and log doubles (Recorder, FakeAPI, LogRecorder); allocation measurement (Measure, MeasureMin, Spread, FloorCall); response bodies, from the module's testdata directory and generated (Fixture, UnknownAnswerFlood); and the fuzz targets' per-input bound and guard pages (BoundFuzzInput, NewGuard).
The package imports neither the SDK's root package nor internal/codec, so the packages that test the transport can use it without sonic. It is the only package of the module that imports golang.org/x/net.
Index ¶
- Constants
- Variables
- func AnswerH2ExampleCom(w http.ResponseWriter, r *http.Request)
- func AssertNotPrinted(tb testing.TB, err error, secret string)
- func BoundFuzzInput(tb testing.TB) (disarm func())
- func CatchFault(f func()) (msg string, faulted bool)
- func ClientTLSConfig(tb testing.TB) *tls.Config
- func ErrorTexts(err error) []string
- func Fixture(tb testing.TB, name string) []byte
- func FixtureNames(tb testing.TB, pattern string) []string
- func FixtureString(tb testing.TB, name string) string
- func FloorCall(rt http.RoundTripper, req *http.Request) error
- func FloorRequest(u *url.URL, h http.Header, body []byte) (*http.Request, *bytes.Reader)
- func IsConnReset(err error) bool
- func QuietRuntime(tb testing.TB)
- func RefusedAddr(tb testing.TB) string
- func RootCAs(tb testing.TB) *x509.CertPool
- func StructuredLegendFlood(levels int) []byte
- func UnknownAnswerFlood(size int) (body []byte, answers int)
- func WaitUntil(tb testing.TB, what string, cond func() bool)
- type ALPN
- type Action
- type Allocs
- type BodySum
- type ConnInfo
- type ErrCode
- type FakeAPI
- type FakeH2CServer
- type GatedDialer
- type Guard
- type H2Conn
- type LogRecord
- type LogRecorder
- type LoopbackServer
- func (s *LoopbackServer) Accepts() int
- func (s *LoopbackServer) Addr() string
- func (s *LoopbackServer) Close()
- func (s *LoopbackServer) CloseConns()
- func (s *LoopbackServer) Conns() []ConnInfo
- func (s *LoopbackServer) LiveH2Conns() []*H2Conn
- func (s *LoopbackServer) MaxActiveStreams() int
- func (s *LoopbackServer) OverLimit() int
- func (s *LoopbackServer) Requests() []SeenRequest
- func (s *LoopbackServer) URL() string
- type PeerSettings
- type Proxy
- type ProxyConnect
- type ProxyMode
- type RecordedRequest
- type Recorder
- type Reply
- type RoundTripFunc
- type Routes
- type SeenRequest
- type ServerConfig
- type SilentListener
- type Stream
Constants ¶
const AllocAgree = 3
AllocAgree is how many of the AllocRuns runs must equal the minimum for MeasureMin and StableMin to accept it.
The runtime's counters are process-wide: a goroutine the test did not start (the testing package's own bookkeeping, a timer, a finalizer) can only add to a section, never take from it, and a repeated identical call costs the SDK the same every time by design, so the minimum is the SDK's cost. The minimum alone would hide an allocation made in some calls and not in others, so it has to be the value of at least AllocAgree runs; a run polluted by foreign work is visible in the printed runs, and when it splits across enough runs the check fails (a flake), never passes falsely. The same rule as the Rust port's tests/support/mod.rs.
const AllocRuns = 5
AllocRuns is the number of times MeasureMin measures a section.
const DuplicatesLastWins = `` /* 454-byte string literal not displayed */
DuplicatesLastWins is testdata/duplicates.json with every repeated member resolved as Python 0.7.1 resolves it: the last one wins at every level, and a repeated object replaces the earlier one whole (usage loses output_tokens).
const FloodAnswer = "flood"
FloodAnswer is the name of the one score answer whose legend StructuredLegendFlood fills.
const FuzzInputBound = 10 * time.Second
FuzzInputBound is the time one fuzz input may run: an input that takes longer is a finding, as a hang past libFuzzer's -timeout=10 is for the Rust SDK's targets.
Variables ¶
var ErrConnClosing = errors.New("testsupport: connection closing")
ErrConnClosing is what H2Conn.SetMaxConcurrentStreams returns, alone or wrapped, when the connection's close had begun before its SETTINGS frame could leave: GOAWAY drained it, the client closed it, or H2Conn.Close or H2Conn.Reset ran. The limit was not delivered, and the connection serves no new stream. LoopbackServer.LiveH2Conns lists a connection until its reader has stopped, so a test that walks that list can meet one.
Functions ¶
func AnswerH2ExampleCom ¶
func AnswerH2ExampleCom(w http.ResponseWriter, r *http.Request)
AnswerH2ExampleCom answers 200 "h2 example.com" to an HTTP/2 request for example.com and 400 to anything else, so a test can check both from the body.
func AssertNotPrinted ¶
AssertNotPrinted fails the test when any printed form of err, or of an error it wraps, contains secret.
func BoundFuzzInput ¶
BoundFuzzInput arms the per-input bound for the fuzz input tb runs and returns the function that disarms it; a fuzz function starts with defer testsupport.BoundFuzzInput(t)().
Go's fuzzing engine has no per-input timeout (cmd/go sets no kill timeout while fuzzing, and internal/fuzz only stops a worker the coordinator has already cancelled), and a context cannot interrupt a decoder that never returns, so the bound is a watchdog: when the input runs past FuzzInputBound the watchdog panics on its own goroutine, which ends the process. Under go test -fuzz the coordinator then reports the worker as "hung or terminated unexpectedly" and writes the input to testdata/fuzz; a plain go test run of the seed corpus fails with the panic, which names the input.
func CatchFault ¶
CatchFault runs f with the runtime's panic on a memory fault turned on for the goroutine (runtime/debug.SetPanicOnFault), and reports whether f faulted and the runtime's message. A panic that is not a fault is passed on.
func ClientTLSConfig ¶
ClientTLSConfig returns a client configuration that trusts the package's certificate and requires TLS 1.2 or later. The caller sets NextProtos (or lets net/http set them from its Protocols).
func ErrorTexts ¶
ErrorTexts returns every way err and each error it wraps can be printed: %v, %+v, %#v, %s and %q.
func Fixture ¶
Fixture returns a fresh copy of the bytes of testdata/<name>. The cache behind it is never handed out, so a caller may modify the slice. An allocation test that must not pay for the copy inside its measured section calls Fixture before the section or uses FixtureString.
func FixtureNames ¶
FixtureNames returns the sorted names of the files directly in testdata that match pattern (path.Match syntax), such as "malformed-*.json". It fails the test when nothing matches, so a renamed fixture set cannot turn a loop over it into a test that checks nothing.
func FixtureString ¶
FixtureString returns the content of testdata/<name> (a slash-separated path such as "result.json"). The file is read once per process; later calls return the same cached string without copying.
func FloorCall ¶
func FloorCall(rt http.RoundTripper, req *http.Request) error
FloorCall is the floor of one whole SDK call: rt called with a request built beforehand, its response drained into io.Discard and closed. No client can cost less. TestAllocWholeCall (internal/alloctest) counts its allocations and the call/floor benchmark times it, so the two measure one floor. It returns the transport's error, or the read's, or the close's, in that order.
func FloorRequest ¶
FloorRequest returns the request FloorCall sends for a call to u with header h and body: a POST built beforehand over a rewindable reader of body, and that reader, which the caller rewinds before each round trip.
func IsConnReset ¶
IsConnReset reports whether err is a TCP reset from the peer: ECONNRESET, or WSAECONNRESET (10054), the code Windows reports for it. The message differs by system, so the check compares codes, not text.
func QuietRuntime ¶
QuietRuntime prepares the process for allocation counting until tb's test ends: it runs a collection, turns the collector off (debug.SetGCPercent(-1)), so a collection cannot empty a sync.Pool between calls, and sets GOMAXPROCS to 1, so a goroutine cannot migrate to a P whose pool-local slot is empty (sync.Pool.Put fills the current P's private slot). Both settings are restored by tb.Cleanup.
The settings are process-wide, so a test that calls QuietRuntime must not run in parallel with other tests (no t.Parallel). Allocation budgets belong in files built with //go:build !race: under the race detector sync.Pool.Put drops one value in four.
func RefusedAddr ¶
RefusedAddr returns a loopback address that refuses every connection until the test ends, on every OS: the local end of a TCP connection the test holds open, where nothing listens.
The connection holds the port while the test runs, so a listener that asks for any port (127.0.0.1:0), as the tests' listeners do, is not given it on darwin or Linux (Windows is unmeasured). A closed listener's address would not do: it is free for anyone at once, and under load another listener can take it before the dial that was meant to be refused.
func RootCAs ¶
RootCAs returns a fresh pool that trusts the certificate every TLS listener of this package serves (the loopback server and the TLS proxies). The certificate covers 127.0.0.1, ::1, localhost, example.com and *.example.com.
func StructuredLegendFlood ¶
StructuredLegendFlood returns a System One response body whose answers are a noul answer "spam", a choice answer "tone" and a score answer named FloodAnswer with the given number of levels (at least 1), every one of them structured: an even level i maps to {"summary":"level i","examples":["example i"]} and an odd one to ["level i",{"note":null}], so the lazy pass reads both structured shapes. The score answer's probabilities give every level 1/levels, its score is the mean level (levels-1)/2 and its confidence 1/levels.
The output is compact JSON without escapes or a trailing newline, and it is deterministic: testdata/structured-legend-flood-1k.json and -10k.json are this function's output for 1000 and 10000 levels (the golden test in flood_test.go regenerates them with -update).
func UnknownAnswerFlood ¶
UnknownAnswerFlood returns a System One response body of at least size bytes and the number of answers it holds: after a model and a usage, only answers of the type "x", which no version of the SDK models, each named "a" and its index and holding nothing but its type:
{"model":"m","usage":{"input_tokens":1,"output_tokens":1},"answers":{"a0":{"type":"x"},"a1":{"type":"x"},…}}
It is a hostile body: a decode keeps one answer entry for each of about 20 bytes of it, so its scratch outgrows the body many times over. The body is compact, deterministic and decodes without a failure, every answer skipped.
Types ¶
type ALPN ¶
type ALPN int
ALPN selects what a LoopbackServer offers during the TLS handshake.
const ( // ALPNH2 offers h2 and http/1.1: a client that offers h2 speaks HTTP/2 // through the server's own frame writer, one that offers only http/1.1 // speaks HTTP/1.1. ALPNH2 ALPN = iota // ALPNNone offers nothing: every handshake completes with no protocol // negotiated, and the connection speaks HTTP/1.1. ALPNNone // ALPNHTTP1Only offers http/1.1 alone: a client that offers only h2 is // refused with TLS alert 120 (no_application_protocol), and one that also // offers http/1.1 speaks HTTP/1.1. ALPNHTTP1Only )
type Action ¶
type Action int
Action is what a LoopbackServer does with an HTTP/2 stream once its request headers have arrived. ServerConfig.OnStream chooses it.
const ( // ActionServe hands the stream to the server's handler. ActionServe Action = iota // ActionRefuse resets the stream with REFUSED_STREAM: the request was not // processed, so the client may retry it. ActionRefuse // ActionGoAway sends GOAWAY with a LastStreamID below this stream (its ID // minus 2), so this stream and any later one are unprocessed; streams // already being served finish, then the connection closes gracefully // ([H2Conn.GoAway]). ActionGoAway // ActionClose closes the connection without GOAWAY, gracefully, as // [LoopbackServer.CloseConns] does: TLS close_notify, then TCP FIN, so // the client reads io.EOF, and the socket closes once the client has // closed its side. [H2Conn.Close] is the immediate close. ActionClose // ActionHold never answers: the stream stays open until the client resets // it or the connection closes. It counts against MAX_CONCURRENT_STREAMS. ActionHold // ActionReset ends the connection with a TCP reset ([H2Conn.Reset]): // no GOAWAY and no close_notify, so the client's read fails with // ECONNRESET. ActionReset )
type Allocs ¶
type Allocs struct {
// Mallocs is the number of heap objects allocated
// (runtime.MemStats.Mallocs).
Mallocs uint64
// Bytes is the number of heap bytes allocated
// (runtime.MemStats.TotalAlloc).
Bytes uint64
}
Allocs is the change in the runtime's allocation counters across one measured section.
func Measure ¶
func Measure(body func()) Allocs
Measure runs body once and returns the change in the allocation counters across it, read with runtime.ReadMemStats before and after. It does not warm anything up: unlike testing.AllocsPerRun, whose warm-up call would consume pool state, the caller decides what runs before the section.
func MeasureMin ¶
MeasureMin measures body AllocRuns times, each on a fresh input() made outside the measured section, logs every run, and returns the minimum that at least AllocAgree runs share (see StableMin). It fails the test when QuietRuntime is not in effect or when the runs do not agree.
A caller that needs a warm pool runs the call once before MeasureMin.
func Spread ¶
Spread logs runs and returns their minimum and their maximum, each taken per counter. Unlike StableMin it does not ask the runs to agree: it is for a section a test checks against a bound on every run, not for an exact pin, which stays with StableMin.
A section can cost a few allocations more in one run than in another for a reason outside the code it measures: the runtime builds a type assertion's or a type switch's cache on about one in 1024 of the lookups that miss it, at random (runtime/iface.go, typeAssert and interfaceSwitch), an allocation of 48 B or more, and a pooled object the collection before a run emptied is allocated anew. A bound holds on every run whatever those add; the minimum is the section's own cost.
func StableMin ¶
StableMin logs runs and returns their minimum, taken per counter; it fails the test when fewer than AllocAgree runs equal that minimum in both counters, because the section does not cost the same on identical calls.
type BodySum ¶
BodySum is the SHA-256 digest of a request body.
func SumGetBody ¶
SumGetBody opens a new reader with getBody (a request's GetBody), reads it to the end, closes it, and returns the SHA-256 digest of the bytes and their number. A test calls it once per attempt to assert that every attempt of a call, and every replay the transport makes, sends the same bytes, and compares the count with the request's ContentLength. A nil getBody is an error, as is a read or close failure.
It allocates the hash state and one 4 KiB read buffer per call, whatever the size of the body.
type ConnInfo ¶
type ConnInfo struct {
// Index is the connection's position in accept order, from 0.
Index int
// Protocol is the ALPN protocol negotiated ("h2", "http/1.1" or "").
Protocol string
// HandshakeErr is the server side's TLS handshake error, if any (for
// example the refusal behind alert 120 in ALPNHTTP1Only mode).
HandshakeErr string
// GoAwaySeq, CloseWriteSeq, PeerClosedSeq and ClosedSeq record how an
// HTTP/2 connection ended, as numbers of one sequence the server shares
// across its connections, so they order events without a clock; 0 means
// the event did not happen. GoAwaySeq is the last GOAWAY frame, taken
// before it is written; CloseWriteSeq is the server beginning its
// graceful close ([LoopbackServer.CloseConns], ActionClose, or the end
// of a connection after GOAWAY once no stream at or below its
// LastStreamID is left), taken before its close_notify and FIN leave;
// PeerClosedSeq is the reader reading the end of the client's side;
// ClosedSeq is the socket's close. 0 < PeerClosedSeq < ClosedSeq shows
// that the socket closed with nothing the client sent left unread, and
// CloseWriteSeq < PeerClosedSeq that the server's close came first. The
// client may close first: net/http closes a connection that GOAWAY ended
// as soon as its last stream is done, and then PeerClosedSeq precedes
// CloseWriteSeq, or CloseWriteSeq is 0.
GoAwaySeq, CloseWriteSeq, PeerClosedSeq, ClosedSeq int64
// Drained lists the types of the frames the reader read and discarded,
// answering none, once the graceful close had begun: what the client
// was still sending when the server's close_notify reached it.
Drained []string
}
ConnInfo describes one accepted connection.
type ErrCode ¶
type ErrCode uint32
ErrCode is an HTTP/2 error code (RFC 9113 section 7), as sent in GOAWAY and RST_STREAM frames. It is this package's own type so a caller need not import golang.org/x/net. H2Conn.GoAway takes any code: ErrCode(n) for one not named here.
const ( // CodeNoError is the code of ActionGoAway's GOAWAY and of the RST_STREAM // that stops an upload the handler answered without reading. CodeNoError ErrCode = 0x0 // CodeInternalError is the code of the RST_STREAM after a handler panics. CodeInternalError ErrCode = 0x2 )
The HTTP/2 error codes the server's own actions send. Besides these, the server answers a frame that the framer rejects as a stream error with RST_STREAM carrying the framer's code (for example PROTOCOL_ERROR).
type FakeAPI ¶
type FakeAPI struct {
// contains filtered or unexported fields
}
FakeAPI is an in-process stand-in for the TypeSafe API, for tests that run programs written against the real one (the examples under examples/). It answers:
- a request without an Authorization header: 401 with the API's authentication error body;
- GET /v1/models: one stand-in model, jev-latest;
- POST /v1/systemone: every question of the request answered by its type, with fixed values that the SDK's typed checks accept: a noul 0.75; a choice the first of its option labels in byte order, with probability 1 and 0 for the others; a score its highest level, with probability 1, and its levels as the legend, each level's JSON as the request sent it. A question of another type gets no answer. The response names the request's model and counts the request's bytes as its input tokens;
- a System One body whose top level holds a member other than state, model and questions (an ExtraBody the API does not know): 400 with the API's api_usage_error body, as the live API answers it;
- a System One body that is not an object with state, model and questions: 422 with a detail list, as the API's validation does;
- anything else: 404.
Every response carries X-Typesafe-Request-Id "req_fake_<n>", n counting from 1. It is safe for concurrent use.
type FakeH2CServer ¶
FakeH2CServer is an net/http/httptest server on the in-memory network (httptest.NewTestServer) that speaks HTTP/2 over cleartext with prior knowledge. It works inside a testing/synctest bubble when created there.
Its Client's transport has Protocols set to unencrypted HTTP/2 only and dials the in-memory listener whatever the request's host, so a request to "http://example.com/..." reaches the server as h2c. The server refuses HTTP/1.1 and TLS, so a request that fell back to either fails loudly. A test that needs its own transport starts from Client().Transport (the DialContext that reaches the in-memory network lives there).
func NewFakeH2CServer ¶
func NewFakeH2CServer(tb testing.TB, handler http.Handler) *FakeH2CServer
NewFakeH2CServer returns a started FakeH2CServer serving handler (nil answers 500, as httptest.NewTestServer does). httptest closes it through tb.Cleanup.
func (*FakeH2CServer) Accepts ¶
func (s *FakeH2CServer) Accepts() int
Accepts returns the number of connections the server accepted so far.
type GatedDialer ¶
type GatedDialer struct {
// contains filtered or unexported fields
}
GatedDialer holds every dial until a gate channel is closed, then dials. It gives a test a dial that completes when the test says so: a cold-start leader cancelled while its dial is in flight, or a waiter whose bound expires before the connection exists. Install GatedDialer.DialContext as the client transport's DialContext, wait for GatedDialer.Waiting to show the dial, act, then close the gate.
func NewGatedDialer ¶
func NewGatedDialer(gate <-chan struct{}, dial func(ctx context.Context, network, addr string) (net.Conn, error)) *GatedDialer
NewGatedDialer returns a dialer whose dials wait for gate to be closed and then go through dial, such as a Routes DialContext; a nil dial uses a zero net.Dialer.
func (*GatedDialer) DialContext ¶
DialContext waits until the gate is closed and then dials addr. When ctx has ended by then, whether first or while the gate was already closed, it returns ctx's error without dialing. It has the signature of net/http.Transport.DialContext.
func (*GatedDialer) Dials ¶
func (g *GatedDialer) Dials() int
Dials returns the number of dials that passed the gate so far.
func (*GatedDialer) Waiting ¶
func (g *GatedDialer) Waiting() int
Waiting returns the number of dials held at the gate now.
type Guard ¶
type Guard struct {
// contains filtered or unexported fields
}
Guard is memory for a reader's input with no readable memory around it: whole pages mapped between two pages that no access may touch (PROT_NONE), so that a read one byte before the first readable byte or one byte past the last one faults instead of reading a neighbour's memory. sonic's native scanner can read up to 4 bytes past a short input (internal/codec's minSonicInput); the Go heap, which always has something after an allocation, cannot show such a read.
func NewGuard ¶
NewGuard maps room for inputs of up to size bytes between two guard pages and unmaps it when the test ends. It needs mmap and mprotect, which the syscall package offers on Linux and Darwin; on any other system it skips the test, saying so.
func (*Guard) AtEnd ¶
AtEnd copies in so that its last byte is the last readable byte, and returns the copy: a read of one byte past it faults.
func (*Guard) AtStart ¶
AtStart copies in so that its first byte is the first readable byte, and returns the copy: a read of the byte before it faults.
func (*Guard) ReadPastEnd ¶
func (g *Guard) ReadPastEnd()
ReadPastEnd reads the byte just past the last readable one, which must fault: the control that the trailing guard is in place. runtime.KeepAlive keeps the compiler from dropping the load.
func (*Guard) ReadPastStart ¶
func (g *Guard) ReadPastStart()
ReadPastStart reads the byte just before the first readable one, which must fault: the control that the leading guard is in place.
type H2Conn ¶
type H2Conn struct {
// contains filtered or unexported fields
}
H2Conn is one HTTP/2 connection of a LoopbackServer, served by the package's frame writer. Its methods are the connection-level knobs; they are safe to call from any goroutine, including from ServerConfig.OnStream.
func (*H2Conn) ActiveStreams ¶
ActiveStreams returns the identifiers of the streams open on the server side (served or held), in ascending order: the streams that count against the stream limit.
func (*H2Conn) Close ¶
func (c *H2Conn) Close()
Close closes the connection at once, without GOAWAY. Closing the TLS connection sends a close_notify alert (unless a frame write is in flight) before the TCP FIN, so the client reads io.EOF, provided the server has read everything the client sent and nothing more arrives: otherwise the kernel answers with a TCP reset, and on Windows the reset destroys what the client has not read yet. LoopbackServer.CloseConns and ActionClose close gracefully instead; H2Conn.Reset ends the connection with a TCP reset.
func (*H2Conn) GoAway ¶
GoAway sends GOAWAY with lastStreamID and code. From then on no stream above lastStreamID is served: the ones already open are dropped (their handlers' contexts are cancelled, their ResponseWriters' writes fail, and nothing more is written for them) and new ones are ignored, as RFC 9113 section 6.8 has it. GOAWAY tells the client that a dropped stream was not processed, but its handler may already have run, in part or up to its last write: a test that counts side effects must not assume otherwise. A dropped stream's SeenRequest shows Dropped. Once every stream at or below lastStreamID has finished, the server ends the connection gracefully, as LoopbackServer.CloseConns does: close_notify and a TCP FIN, then it reads and discards what the client still sends until the client closes its side, and only then closes the socket. The GOAWAY frame always reaches the wire before close_notify. A second call can only lower lastStreamID. On a connection whose graceful close has begun nothing is written, and GoAway returns an error.
func (*H2Conn) Reset ¶
func (c *H2Conn) Reset()
Reset ends the connection abruptly: it sets SO_LINGER to 0 on the TCP socket and closes the socket under the TLS layer, without GOAWAY or close_notify, so the kernel sends a TCP RST and the client's next read fails with ECONNRESET (WSAECONNRESET on Windows) rather than io.EOF.
func (*H2Conn) SetMaxConcurrentStreams ¶
SetMaxConcurrentStreams sends a SETTINGS frame advertising n (at least 1) as SETTINGS_MAX_CONCURRENT_STREAMS and enforces n on this connection from then on: a new stream that would exceed it is reset with REFUSED_STREAM and counted by LoopbackServer.OverLimit, even one the client opened before it read the frame (RFC 9113 section 5.1.2 allows the refusal; a real server may wait for the client's acknowledgement). Streams already open are left alone. It lowers or raises a limit in the middle of a connection. On a connection whose close has begun it changes nothing and returns an error matching ErrConnClosing.
type LogRecord ¶
type LogRecord struct {
// Time is the record's time (zero when the logger set none).
Time time.Time
// Level is the record's level.
Level slog.Level
// Message is the record's message.
Message string
// Attrs are the handler's and the record's attributes, flattened.
Attrs []slog.Attr
}
LogRecord is one record a LogRecorder kept. Its attributes are resolved (a LogValuer is replaced by its value) and flattened: an attribute inside group "g" has the key "g.key", with the handler's groups (from WithGroup) first.
type LogRecorder ¶
type LogRecorder struct {
// contains filtered or unexported fields
}
LogRecorder keeps every record logged through its handler (and through handlers derived from it with WithAttrs and WithGroup). The zero value is ready to use and keeps every record, as NewLogRecorder(nil) does. It is safe for concurrent use.
func NewLogRecorder ¶
func NewLogRecorder(level slog.Leveler) *LogRecorder
NewLogRecorder returns a recorder that keeps the records at or above level; a nil level keeps every record, including the SDK's trace level below slog.LevelDebug.
func (*LogRecorder) At ¶
func (r *LogRecorder) At(level slog.Level) []LogRecord
At returns the records at exactly level, in logging order.
func (*LogRecorder) Handler ¶
func (r *LogRecorder) Handler() slog.Handler
Handler returns a log/slog.Handler that records into r.
func (*LogRecorder) Logger ¶
func (r *LogRecorder) Logger() *slog.Logger
Logger returns a logger over LogRecorder.Handler.
func (*LogRecorder) Records ¶
func (r *LogRecorder) Records() []LogRecord
Records returns a copy of the records kept so far, in logging order.
type LoopbackServer ¶
type LoopbackServer struct {
// contains filtered or unexported fields
}
LoopbackServer is a TLS server on 127.0.0.1 for transport tests. HTTP/2 connections are served by the package's own frame writer (x/net's http2.Framer and hpack), which gives the knobs of ServerConfig and H2Conn; HTTP/1.1 connections (ALPN http/1.1 or none) by net/http.
Clients trust it with RootCAs or ClientTLSConfig. Close runs from tb.Cleanup.
func NewFixtureServer ¶
func NewFixtureServer(tb testing.TB) *LoopbackServer
NewFixtureServer starts a LoopbackServer that answers the way the benchmarks' calls expect: models.json for a path ending in /v1/models and result.json for every other request, each with a Content-Type of application/json and a Content-Length, after reading the whole request body as a real server would. Close runs from tb.Cleanup.
func NewLoopbackServer ¶
func NewLoopbackServer(tb testing.TB, cfg ServerConfig) *LoopbackServer
NewLoopbackServer starts a server on 127.0.0.1 with a free port and registers its Close with tb.Cleanup.
func (*LoopbackServer) Accepts ¶
func (s *LoopbackServer) Accepts() int
Accepts returns the number of TCP connections accepted so far.
func (*LoopbackServer) Addr ¶
func (s *LoopbackServer) Addr() string
Addr returns the listener's address, 127.0.0.1:port.
func (*LoopbackServer) Close ¶
func (s *LoopbackServer) Close()
Close stops the listener, closes every connection and waits for the server's goroutines, including running handlers, which see their request context cancelled. It is idempotent.
func (*LoopbackServer) CloseConns ¶
func (s *LoopbackServer) CloseConns()
CloseConns ends every open connection without GOAWAY; the listener stays open. An HTTP/2 connection ends as a server that is done with it should (as ActionClose and the end after GOAWAY do too): its streams are dropped, TLS close_notify and a TCP FIN follow the frames already written, nothing is written after them, and the server keeps reading, discarding whatever the client still sends, until the client closes its side or a bounded drain (5 s) passes; only then does it close the socket, since a socket closed with the client's data unread ends with a TCP reset, which on Windows destroys what the client has not read yet. It returns once close_notify and FIN are sent, without waiting for the client, and ConnInfo.CloseWriteSeq and the records after it tell how the close went. Any other connection (HTTP/1.1, or one still in its handshake) is closed at once: close_notify when past the handshake, then FIN.
func (*LoopbackServer) Conns ¶
func (s *LoopbackServer) Conns() []ConnInfo
Conns returns what is known of every accepted connection, in accept order.
func (*LoopbackServer) LiveH2Conns ¶
func (s *LoopbackServer) LiveH2Conns() []*H2Conn
LiveH2Conns returns the HTTP/2 connections still open, in accept order.
func (*LoopbackServer) MaxActiveStreams ¶
func (s *LoopbackServer) MaxActiveStreams() int
MaxActiveStreams returns the largest number of HTTP/2 streams that were open at once on any one connection (served or held).
func (*LoopbackServer) OverLimit ¶
func (s *LoopbackServer) OverLimit() int
OverLimit returns the number of streams reset because they would have exceeded MaxConcurrentStreams.
func (*LoopbackServer) Requests ¶
func (s *LoopbackServer) Requests() []SeenRequest
Requests returns every request seen so far, in arrival order.
func (*LoopbackServer) URL ¶
func (s *LoopbackServer) URL() string
URL returns "https://127.0.0.1:port".
type PeerSettings ¶
type PeerSettings struct {
// Protocol is the ALPN protocol the TLS handshake negotiated: "h2".
Protocol string
// Values holds every setting of the frame by its RFC 9113 name, such as
// "MAX_CONCURRENT_STREAMS"; a setting the server leaves out keeps its
// initial value and has no entry (for MAX_CONCURRENT_STREAMS, no limit).
Values map[string]uint32
// Handshake is the time from the start of the dial to the end of the
// TLS handshake; FirstSettings the time from the start of the dial to
// the arrival of the SETTINGS frame.
Handshake, FirstSettings time.Duration
}
PeerSettings is what an HTTP/2 server announces in the first SETTINGS frame of a connection (RFC 9113 section 6.5), as ReadPeerSettings reads it.
func ReadPeerSettings ¶
ReadPeerSettings dials addr ("host:port") over TLS offering only h2, writes the client connection preface and an empty SETTINGS frame, and returns the settings of the server's first SETTINGS frame. It sends no request, so no credential is involved; it closes the connection with GOAWAY before it returns. cfg is cloned and its NextProtos replaced by "h2"; a nil cfg verifies the server against the system roots, with the server name taken from addr. A handshake that negotiates another protocol, or a first frame that is not SETTINGS, is an error.
func (PeerSettings) MaxConcurrentStreams ¶
func (s PeerSettings) MaxConcurrentStreams() (uint32, bool)
MaxConcurrentStreams returns the server's SETTINGS_MAX_CONCURRENT_STREAMS and whether the frame carried it.
type Proxy ¶
type Proxy struct {
// contains filtered or unexported fields
}
Proxy is an HTTP/1.1 CONNECT proxy on 127.0.0.1. It dials the requested authority through its Routes (never the network) and tunnels bytes both ways. Clients select it with http.ProxyURL(p.URL()); a TLS proxy trusts the package certificate like the LoopbackServer. Close runs from tb.Cleanup.
func (*Proxy) Close ¶
func (p *Proxy) Close()
Close stops the listener, closes every connection and tunnel, and waits for the proxy's goroutines. It is idempotent.
func (*Proxy) Connects ¶
func (p *Proxy) Connects() []ProxyConnect
Connects returns every request handled so far, in arrival order.
func (*Proxy) Conns ¶
Conns returns what is known of every accepted connection: for a TLS proxy the protocol its handshake negotiated or the handshake error.
func (*Proxy) URL ¶
URL returns the proxy URL: http://127.0.0.1:port for ProxyPlain, https://127.0.0.1:port otherwise.
type ProxyConnect ¶
type ProxyConnect struct {
// Conn is the index of the proxy connection it arrived on.
Conn int
// Target is the requested authority, such as "example.com:443".
Target string
// Status is the status the proxy answered: 200 when the tunnel opened,
// 502 when the target had no route or could not be dialed, 405 for a
// method other than CONNECT.
Status int
}
ProxyConnect is one CONNECT request a Proxy handled.
type ProxyMode ¶
type ProxyMode int
ProxyMode selects how a Proxy is reached and what its TLS layer offers.
const ( // ProxyPlain is a plain TCP proxy ("http://" proxy URL). ProxyPlain ProxyMode = iota // ProxyTLSLenient is a TLS proxy without ALPN ("https://" proxy URL): it // accepts any client offer and negotiates nothing, so an h2-only client // hop completes. ProxyTLSLenient // ProxyTLSStrict is a TLS proxy that offers http/1.1 alone: a client that // offers only h2 on the proxy hop is refused with alert 120 // (no_application_protocol). ProxyTLSStrict // ProxyTLSOfferH2 is a TLS proxy that offers h2 and http/1.1, so an // h2-offering client negotiates h2 on the proxy hop, although the proxy // still expects an HTTP/1.1 CONNECT. ProxyTLSOfferH2 )
type RecordedRequest ¶
type RecordedRequest struct {
// Index is the request's position among all RoundTrip calls, from 0.
Index int
// Method is the request method.
Method string
// URL is the request URL as a string; empty when the Recorder discards.
URL string
// Host is the request's Host (the authority the transport would send).
Host string
// Header is a copy of the request header.
Header http.Header
// Body is the whole request body, read before RoundTrip returned; nil
// when the request had none or the Recorder discards bodies.
Body []byte
// ContentLength is the request's declared length.
ContentLength int64
// HasGetBody reports whether the request carried GetBody, which a
// transport needs to replay it.
HasGetBody bool
// Canceled reports that the request's context was already done, so the
// Recorder returned the context's error and served no reply.
Canceled bool
}
RecordedRequest is what a Recorder saw of one request.
type Recorder ¶
type Recorder struct {
// Replies are served in order; once they run out, the last one repeats.
Replies []Reply
// Respond, when set, chooses the reply for each request instead of
// Replies. It runs on the goroutine that called RoundTrip and gets its
// own copy of the record, which it may change.
Respond func(RecordedRequest) Reply
// Discard drains and closes each request body without keeping it, and
// keeps only the count of requests, so a steady-state allocation test
// measures little beyond the response it is handed. Requests then
// returns nothing and Respond sees a nil Body.
Discard bool
// contains filtered or unexported fields
}
Recorder is an in-memory net/http.RoundTripper that records every request and answers with canned replies. It is synchronous: RoundTrip reads the whole request body and closes it before it returns, so a body's lifetime is in step with the call (the HTTP/2 transport closes bodies in a goroutine).
Set the exported fields before the first RoundTrip and do not change them afterwards. A Recorder is safe for concurrent use.
func (*Recorder) Close ¶
Close counts a call, so a lifecycle test can assert that the SDK closes a supplied io.Closer transport exactly once. It always returns nil. A test that needs a transport without Close wraps the Recorder in a struct that embeds only http.RoundTripper.
func (*Recorder) Requests ¶
func (r *Recorder) Requests() []RecordedRequest
Requests returns copies of the requests recorded so far, in call order; changing one changes neither the Recorder's record nor a later copy.
type Reply ¶
type Reply struct {
// Status is the response status code; zero means 200.
Status int
// Header is copied into the response.
Header http.Header
// Body is the response body. The Recorder never modifies it.
Body []byte
// ContentLength is the length the response declares. Zero means
// len(Body); -1 declares no length (a chunked body); a value above
// len(Body) makes the body end with io.ErrUnexpectedEOF after len(Body)
// bytes, as a connection that closes early would. A declared zero with a
// non-empty body cannot be expressed.
ContentLength int64
// Err, when set, is returned by RoundTrip instead of a response.
Err error
}
Reply is the canned outcome of one round trip through a Recorder: a transport error when Err is set, otherwise a response.
type RoundTripFunc ¶
RoundTripFunc adapts a function to http.RoundTripper.
type Routes ¶
Routes maps a dial address, host:port as net/http passes it to a dialer (for example "example.com:443"), to the loopback address that serves it.
A test that uses example.com as the API host (covered by the package certificate) routes it to a LoopbackServer with Routes, both in the client transport's DialContext and in a Proxy.
func (Routes) DialContext ¶
DialContext dials the address Routes.Resolve returns for addr. It has the signature of net/http.Transport.DialContext.
type SeenRequest ¶
type SeenRequest struct {
// Seq is the request's position among all requests seen, from 0.
Seq int
// Conn is the index of the connection it arrived on (see ConnInfo).
Conn int
// StreamID is the HTTP/2 stream identifier; 0 for HTTP/1.1.
StreamID uint32
// Proto is "HTTP/2.0" or "HTTP/1.1".
Proto string
// Method, Path and Authority are the request's method, request target
// and authority (Host).
Method, Path, Authority string
// Header holds the regular header fields.
Header http.Header
// Action is what the server did with it; always ActionServe for
// HTTP/1.1. A stream refused for exceeding MaxConcurrentStreams shows
// ActionRefuse.
Action Action
// Dropped reports that the stream ended before the server finished its
// response: GOAWAY dropped it, the client reset it, or its connection
// closed. Action keeps what the server chose first, so a stream whose
// handler had started shows ActionServe and Dropped. The flag is decided
// when the frame that ends the server's side is cleared for writing: a
// stream whose final frame was cleared but whose write then failed shows
// neither Dropped nor a completed response. Always false for HTTP/1.1.
Dropped bool
}
SeenRequest is a request as a LoopbackServer recorded it: at the moment its headers arrived, whether or not it was then served.
type ServerConfig ¶
type ServerConfig struct {
// ALPN selects the protocols offered in the TLS handshake.
ALPN ALPN
// MaxConcurrentStreams, when non-zero, is advertised in the server's
// SETTINGS frame; a stream that would exceed it is reset with
// REFUSED_STREAM and counted by [LoopbackServer.OverLimit]. It is each
// HTTP/2 connection's limit until [H2Conn.SetMaxConcurrentStreams]
// changes it.
MaxConcurrentStreams uint32
// Handler serves every request, HTTP/2 and HTTP/1.1. Nil answers 200 with
// an empty body. Over HTTP/2 the response carries no Content-Length unless
// the handler sets one, so a test can declare a length that differs from
// the body or none at all.
Handler http.Handler
// OnStream, when set, chooses what happens to each HTTP/2 stream when its
// request headers arrive; nil serves every stream. It runs on the
// connection's reader goroutine, so it must not block; it may call the
// stream's connection methods (GoAway, Close).
OnStream func(*Stream) Action
}
ServerConfig configures a LoopbackServer.
type SilentListener ¶
type SilentListener struct {
// contains filtered or unexported fields
}
SilentListener accepts TCP connections on 127.0.0.1 and never reads or writes a byte: a TLS client's handshake to it never completes, so a dial fails only when the client's own bound (TLSHandshakeTimeout, a context) expires. Close runs from tb.Cleanup.
func NewSilentListener ¶
func NewSilentListener(tb testing.TB) *SilentListener
NewSilentListener starts a silent listener with a free port.
func (*SilentListener) Accepts ¶
func (l *SilentListener) Accepts() int
Accepts returns the number of TCP connections accepted so far.
func (*SilentListener) Addr ¶
func (l *SilentListener) Addr() string
Addr returns the listener's address, 127.0.0.1:port.
func (*SilentListener) Close ¶
func (l *SilentListener) Close()
Close stops the listener and closes every accepted connection. It is idempotent.
func (*SilentListener) URL ¶
func (l *SilentListener) URL() string
URL returns "https://127.0.0.1:port".
type Stream ¶
type Stream struct {
// Conn is the connection the stream arrived on.
Conn *H2Conn
// ID is the stream identifier.
ID uint32
// Seq is the stream's position among every request the server has seen,
// from 0.
Seq int
// Method, Path and Authority are the :method, :path and :authority
// pseudo-headers.
Method, Path, Authority string
// Header holds the regular header fields.
Header http.Header
}
Stream is an HTTP/2 request stream as ServerConfig.OnStream sees it.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package naive is the benchmark comparator of the SDK's performance criteria (docs/perf/frozen-budgets.md): a System One call written the obvious way, with a general-purpose JSON library and no SDK machinery.
|
Package naive is the benchmark comparator of the SDK's performance criteria (docs/perf/frozen-budgets.md): a System One call written the obvious way, with a general-purpose JSON library and no SDK machinery. |