Repository navigation
Strict Client
Package client (doc.go) is a test client for NNTP
servers. In strict mode it reports wire-level mistakes that lenient
newsreaders hide.
| Function | Effect |
|---|---|
Dial(t, addr) |
connect, read the greeting, close when the test ends; fail the test on error |
DialWith(t, addr, deadline) |
the same with its own deadline |
TryDial(addr, deadline) |
the same without testing; close it yourself |
New(t, conn) |
wrap an open net.Conn (TLS, net.Pipe, proc stdio); no greeting is read |
c.WithTB(t) |
attach a test to a TryDial connection |
c.For(t) |
a view of c that fails t instead, for subtests sharing one connection |
All of them turn strict mode on. c.Greeting holds the greeting line.
A deadline of 0 is the hang guard (NNTP_TEST_DEADLINE, default 60s).
| Method | Effect |
|---|---|
Cmd(format, args...) |
send one line and CRLF, read the status: (code, line); fails the test on error |
TryCmd(...) |
the same, returning the error |
ReadDotLines() / TryReadDotLines()
|
a multi-line reply: un-stuffed, line ends stripped |
RawUntilDot() / TryRawUntilDot()
|
the exact bytes up to and including the terminator |
Send(raw) |
raw bytes, nothing added: for pipelining and POST payloads |
ReadLine(), ReadStatus(), ReadGreeting()
|
one line, one status line, the greeting |
ExpectNoPending(d) |
nothing may arrive within d
|
Compress() / TryCompress()
|
send COMPRESS DEFLATE; on 206 compress both directions (see below) |
StartCompress(), Compressed()
|
compress from now on without sending a command; whether compression is on |
Close() |
safe to call twice, and on nil |
Cmd does not check the code; compare it yourself, or use
client.ExpectCode(code, line, want...). ExpectLines(got, want),
ParseCode and Unstuff are text helpers.
The methods without Try fail the test. On a TryDial connection without
WithTB they panic, so use the Try forms there.
A *client.StrictError with one of these kinds:
| Kind | When |
|---|---|
BareLF |
a line ends in LF without CR |
BareCR |
a CR inside a line |
NoCRLF |
the connection ends (EOF or deadline) after data without a line end |
BadDotStuffing |
a line of a multi-line reply starts with . but is neither .. nor the terminator |
NoTerminator |
a multi-line reply ends without .\r\n
|
Pending |
ExpectNoPending saw bytes (they stay unread; Bytes holds them) |
StrictError has Kind, Line (the raw line), Bytes and Err. Err
is the read error and Unwrap returns it, so errors.Is(err, io.EOF)
works:
lines, err := c.TryReadDotLines()
var serr *client.StrictError
if errors.As(err, &serr) && serr.Kind == client.NoTerminator {
// the reply was cut: lines holds what arrived
}Call ExpectNoPending after a reply to catch a stray byte or a second
status line, for example a NUL after 400 too many users. It never
consumes data and returns nil on timeout or EOF.
With c.Strict = false a bare LF counts as a line end, like a lenient
newsreader.
c.Compress() sends COMPRESS DEFLATE (RFC 8054) and, when the server
answers 206, compresses the connection in both directions from the end of
the 206 line on. TryCompress() returns (code, line, err): any other
reply (403, 500, 501, 502, 503) is an error and the connection
stays plain. After a test sent the command itself and read the 206,
StartCompress() does the switch alone.
c := client.Dial(t, addr)
c.Compress() // 206, or the test fails
c.Cmd("CAPABILITIES") // sent compressed; the reply is inflated
caps := c.ReadDotLines() // must not list COMPRESS DEFLATE any more
if code, line := c.Cmd("COMPRESS DEFLATE"); code != 502 {
t.Errorf("second COMPRESS: %q, want 502", line)
}What stays the same and what to know:
- Every
SendandCmdis deflated (raw DEFLATE, no zlib header) and sync-flushed, so the server can decode each command at once. - Strict mode,
ReadDotLines,RawUntilDotandExpectNoPendingwork on the inflated stream. An empty flush block inflates to nothing, soExpectNoPendingstill waits its time and returns nil. - The inflater passes on each byte as soon as it can be decoded, whatever
flush the server uses. INN's nnrpd ends each response with zlib's
Z_PARTIAL_FLUSH; Go'scompress/flatereader holds such a response back until more data comes, so a Go client built on it hangs on INN. The kit has its own inflater for that reason. - The hang guard and other read deadlines apply to the inflated stream: a timeout loses nothing, and the rest can still be read.
- A server that closes the connection reads as
io.EOF; corrupt compressed data is an error wrappingflate.CorruptInputError. - To send bytes past the compressor (corrupt input for a server test),
keep
c.Connfrom beforeCompress. - The client doesn't stop a test from sending a second
COMPRESS,STARTTLSorAUTHINFO: RFC 8054 wants502for all of them.
nntp-testkit is free software under the GNU General Public License, version 2 — see LICENSE.