VTC(7)
โจ NAME โจ
VTC(7) โ Varnish Test Case Syntax. That's it, that's the name, bestie. ๐
๐ OVERVIEW ๐
Okay so this whole doc is about the syntax used by Varnish Test Cases files (.vtc). A .vtc file describes a scenario with different scripted HTTP-talking entities, and generally one or more Varnish instances to test. It's giving test script energy fr fr. ๐ฆ
๐ PARSING ๐
A .vtc file gets read word after word, with very little tokenization, meaning a syntax error won't be detected until the test actually reaches the relevant action in the test. So like, it's lowkey a surprise party but the surprise is an error. ๐ญ
A parsing error will most of the time result in an assert being triggered. If this happens, please refer yourself to the related source file and line number. However, this guide should help you avoid the most common mistakes. No cap, use this guide. ๐
๐ Words and strings ๐
The parser splits words by detecting whitespace characters and a string is a word, or a series of words on the same line enclosed by double-quotes ("..."), or, for multi-line strings, enclosed in curly brackets ({...}). So quotes or curlies, pick your fighter. โจ
๐ฌ Comments ๐ฌ
The leading whitespaces of lines are ignored. Empty lines (or ones consisting only of whitespaces) are ignored too, as are the lines starting with # that are comments. So go ahead and leave cute notes for yourself, the parser won't judge. ๐
๐ Lines and commands ๐
Test files take at most one command per line, with the first word of the line being the command and the following ones being its arguments. To continue over to a new line without breaking the argument string, you can escape the newline character (\n) with a backslash (\). Slay, line continuation. ๐
๐ช MACROS ๐ช
When a string is processed, macro expansion is performed. Macros are in the form ${<name>[,<args>...]}, they have a name followed by an optional comma- or space-separated list of arguments. Leading and trailing spaces are ignored. So spaces are not the moment, got it. ๐ธ
The macros ${foo,bar,baz} and ${ foo bar baz } are equivalent. If an argument contains a space or a comma, arguments can be quoted. For example the macro ${foo,"bar,baz"} gives one argument bar,baz to the macro called foo. Quoting is the bestie that saves you from comma chaos. ๐
Unless documented otherwise, all macros are simple macros that don't take arguments. Simple like that. ๐
๐๏ธ Built-in macros ๐๏ธ
${bad_backend}โ A socket address that will reliably never accept connections. It's the flaky friend of addresses, but on purpose. ๐${bad_ip}โ An unlikely IPv4 address. Not your average IP, bestie. ๐${date}โ The current date and time formatted for HTTP. Time check, but make it HTTP. โฐ${listen_addr}โ The default listen address various components use, by default a random port on localhost. Random port? Main character behavior. ๐${localhost}โ The first IP address that resolves to"localhost". Classic. ๐ป${pwd}โ The working directory from whichvarnishtestwas executed. Where you at, literally. ๐${string,<action>[,<args>...]}โ The string macro is the entry point for text generation, it takes a specialized action with each its own set of arguments. So it's the manager of string macros. ๐${string,repeat,<uint>,<str>}โ Repeat uint times the string str. Repeat after me: repeat. ๐${testdir}โ The directory containing the VTC script of the ongoing test case execution. Home of the test. ๐${tmpdir}โ The dedicated working directory for the ongoing test case execution, which happens to also be the current working directory. Useful when an absolute path to the working directory is needed. It's giving tmp but make it cute. ๐ง${topbuild}โ Only present when the-ioption is used, to work on Varnish itself instead of a regular installation. Special guest star. โญ
๐ ๏ธ SYNTAX ๐ ๏ธ
๐ง barrier ๐ง
NOTE: This command is available everywhere commands are given. Everywhere, bestie. ๐
Barriers allows you to synchronize different threads to make sure events occur in the right order. It's even possible to use them in VCL. Synchronization? We love a coordinated moment. ๐ซถ
First, it's necessary to declare the barrier:
barrier bNAME TYPE NUMBER [-cyclic]
With the arguments being:
bNAMEโ this is the name of the barrier, used to identify it when you'll create sync points. It must start with'b'. B for barrier, b for bestie. ๐TYPEโ it can be"cond"(mutex) or"sock"(socket) and sets internal behavior. If you don't need VCL synchronization, usecond. Choose your fighter wisely. โ๏ธNUMBERโ number of sync point needed to go through the barrier. Count them, don't play. ๐ข-cyclicโ if present, the barrier will reset itself and be ready for another round once gotten through. Round two, and another round, iconic. ๐
Then, to add a sync point:
barrier bNAME sync
This will block the parent thread until the number of sync points for bNAME reaches the NUMBER given in the barrier declaration. Blocked until it's time, periodt. โณ
If you wish to synchronize the VCL, you need to declare a "sock" barrier. This will emit a macro definition named "bNAME_sock" that you can use in VCL (after importing the vtc vmod):
vtc.barrier_sync("${bNAME_sock}");
This function returns 0 if everything went well and is the equivalent of barrier bNAME sync at the VTC top-level. Zero means good vibes. 0๏ธโฃ
๐ฏ client/server ๐ฏ
Client and server threads are fake HTTP entities used to test your Varnish and VCL. They take any number of arguments, and the one that are not recognized, assuming they don't start with '-', are treated as specifications, laying out the actions to undertake:
client cNAME [...]
server sNAME [...]
Clients and server are identified by a string that's the first argument, clients' names start with 'c' and servers' names start with 's'. C for client, S for server, simple as. ๐
As the client and server commands share a good deal of arguments and specification actions, they are grouped in this single section, specific items will be explicitly marked as such. Sharing is caring, bestie. ๐ซถ
๐๏ธ Arguments ๐๏ธ
-startโ Start the thread in background, processing the last given specification. Go off, background thread. ๐-waitโ Block until the thread finishes. Wait for it, wait for it. โณ-run(client only) โ Equivalent to"-start -wait". The combo meal of options. ๐-repeat NUMBERโ Instead of processing the specification only once, do itNUMBERtimes. Again, again, again. ๐-keepaliveโ For repeat, do not open new connections but rather run all iterations in the same connection. Reuse, reduce, recycle. โป๏ธ-break(server only) โ Stop the server. Not the vibe anymore, bye. ๐-listen STRING(server only) โ Dictate the listening socket for the server.STRINGis of the form"IP PORT", or"/PATH/TO/SOCKET"for a Unix domain socket. In the latter case, the path must begin with'/', and the server must be able to create it. Slash means Unix socket, noted. ๐งฆ-connect STRING(client only) โ Indicate the server to connect to.STRINGis also of the form"IP PORT", or"/PATH/TO/SOCKET". As with"server -listen", a Unix domain socket is recognized whenSTRINGbegins with a'/'. Match made in socket heaven. ๐-dispatch(server only, s0 only) โ Normally, to keep things simple, server threads only handle one connection at a time, but the-dispatchswitch allows to accept any number of connection and handle them following the given spec.
However, -dispatch is only allowed for the server name "s0". Only s0, don't try it with anyone else. ๐ซ
-proxy1 STRING(client only) โ Use the PROXY protocol version 1 for this connection.STRINGis of the form"CLIENTIP:PORT SERVERIP:PORT". Proxy protocol, but make it version 1. 1๏ธโฃ-proxy2 STRING(client only) โ Use the PROXY protocol version 2 for this connection.STRINGis of the form"CLIENTIP:PORT SERVERIP:PORT [TLV [TLV ... ]]".
TLV is in the form name=val
name: 0xID or alpn, authority, crc32c, noop, unique_id, netns val: 0x... or string
ssl is currently not implemented (can be sent as hex). Not implemented, so don't come for me. ๐
๐ค Macros and automatic behaviour ๐ค
To make things easier in the general case, clients will connect by default to a Varnish server called v1. To connect to a different Varnish server, use '-connect ${vNAME_sock}'. Default to v1, or switch it up. ๐
The -vcl+backend switch of the varnish command will add all the declared servers as backends. Be careful though, servers will by default listen to the 127.0.0.1 IP and will pick a random port, and publish 3 macros: sNAME_addr, sNAME_port and sNAME_sock, but only once they are started. For 'varnish -vcl+backend' to create the vcl with the correct values, the server must be started first. Order matters, bestie. Start the server first, no cap. โ ๏ธ
๐ Specification ๐
It's a string, either double-quoted "like this", but most of the time enclosed in curly brackets, allowing multilining. Write a command per line in it, empty line are ignored, and long line can be wrapped by using a backslash. For example:
client c1 {
txreq -url /foo \
-hdr "bar: baz"
rxresp
} -run
accept(server only) โ Close the current connection, if any, and accept a new one. Note that this new connection is HTTP/1.x. New connection, who dis? HTTP/1.x, that's who. ๐chunked STRINGโ SendSTRINGas chunked encoding. Chunk it, send it. ๐ฆchunkedlen NUMBERโ Do aschunkedexcept that the string will be generated for you, with a length ofNUMBERcharacters. Auto-generated, we love convenience. โจclose(server only) โ Close the connection. Note that if operating in HTTP/2 mode no extra (GOAWAY) frame is sent, it's simply a TCP close. No GOAWAY, just goodbye. ๐ชexpect STRING1 OP STRING2โ Test if"STRING1 OP STRING2"is true, and if not, fails the test.OPcan be==,<,<=,>,>=whenSTRING1andSTRING2represent numbers in which case it's an order operator. IfSTRING1andSTRING2are meant as stringsOPis a matching operator, either==(exact match) or~(regex match).
varnishtest will first try to resolve STRING1 and STRING2 by looking if they have special meanings, in which case, the resolved value is use for the test. Note that this value can be a string representing a number, allowing for tests such as:
expect req.http.x-num > 2
Here's the list of recognized strings, most should be obvious as they either match VCL logic, or the txreq/txresp options:
remote.ipremote.portremote.pathreq.methodreq.urlreq.protoresp.protoresp.statusresp.reasonresp.chunklenreq.bodylenreq.bodyresp.bodylenresp.bodyreq.http.NAMEresp.http.NAME
expect_closeโ Reads from the connection, expecting nothing to read but an EOF. Expecting nothing, getting EOF. ๐expect_patternโ Expect as the http body the test pattern generated bychunkedlen('0'..'7'repeating). Pattern girlies, this one's for you. ๐fatal|non_fatalโ Control whether a failure of this entity should stop the test. Fatal or not, you decide the drama level. ๐ญgunzipโ Gunzip the body in place. Unzip it, unbothered. ๐๏ธrecv NUMBERโ ReadNUMBERbytes from the connection. Bytes, bestie. ๐ฅrxchunkโ Receive an HTTP chunk. One chunk at a time. ๐ซrxpri(server only) โ Receive a preface. If valid set the server to HTTP/2, abort otherwise. Valid preface or abort, no in-between. ๐ฆrxreq(server only) โ Receive and parse a request's headers and body. Headers and body, the whole package. ๐ฌrxreqbody(server only) โ Receive a request's body. Just the body, not the headers. ๐งrxreqhdrs(server only) โ Receive and parse a request's headers (but not the body). Headers only, body not invited. ๐rxresp [-no_obj](client only) โ Receive and parse a response's headers and body. If-no_objis present, only get the headers. No object, no body, just headers. ๐
rxrespbody (client only) ๐
Bestie, receive (part of) a response's body. That's it, that's the whole vibe.
-max: max length of this receive,0for all
rxresphdrs (client only) โจ
Receive and parse a response's headers. Get those headers, parse those headers, slay.
send STRING ๐
Push STRING on the connection. Like, literally just yeet it out there.
send_n NUMBER STRING
Write STRING on the socket NUMBER times. The same string, over and over, NUMBER whole times.
send_urgent STRING
Send string as TCP OOB urgent data. Ngl you will never need this. ๐
sendhex STRING
Send bytes as described by STRING. STRING should consist of hex pairs possibly separated by whitespace or newlines. For example:
"0F EE a5 3df2"
settings -dectbl INT
Force internal HTTP/2 settings to certain values. Currently only support setting the decoding table size.
shell
Same as for the top-level shell. No notes. ๐
shutdown
Initiate shutdown. The way that this just... ends things. Iconic.
-readโ Shutdown the read direction.-writeโ Shutdown the write direction.-notconnโ OK to getENOTCONN
The default is both direction.
stream
HTTP/2 introduces the concept of streams, and these come with their own specification, and as it's quite big, have been moved to their own chapter. (Go read that one, bestie, it's a whole thing. ๐ฆ)
timeout NUMBER
Set the TCP timeout for this entity.
txpri (client only) ๐
Send an HTTP/2 preface:
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
and set client to HTTP/2.
txreq|txresp [...]
Send a minimal request or response, but overload it if necessary.
txreq is client-specific and txresp is server-specific.
The only thing different between a request and a response, apart from who can send them is that the first line (request line vs status line), so all the options are pretty much the same.
-method STRING(txreq only) โ What method to use (default:"GET").-req STRING(txreq only) โ Alias for-method.-url STRING(txreq only) โ What location to use (default"/").-proto STRINGโ What protocol use in the status line. (default:"HTTP/1.1").-status NUMBER(txresp only) โ What status code to return (default200).-reason STRING(txresp only) โ What message to put in the status line (default:"OK").-noserver(txresp only) โ Don't include a Server header with the id of the server.-nouseragent(txreq only) โ Don't include a User-Agent header with the id of the client.
These three switches can appear in any order but must come before the following ones.
-nohostโ Don't include a Host header in the request. Also Implied by the addition of a Host header with-hdr.-nolenโ Don't include a Content-Length header. Also implied by the addition of a Content-Length or Transfer-Encoding header with-hdr.-nodateโ Don't include a Date header in the response. Also implied by the addition of a Date header with-hdr.-hdr STRINGโ AddSTRINGas a header, it must follow this format:"name: value". It can be called multiple times.-hdrlen STRING NUMBERโ AddSTRINGas a header withNUMBERbytes of content.
You can then use the arguments related to the body:
-body STRINGโ InputSTRINGas body.-bodyfrom FILEโ Same as-bodybut content is read fromFILE.-bodylen NUMBERโ Generate and input a body that isNUMBERbytes-long.-gziplevel NUMBERโ Set the gzip level (call it before any of the other gzip switches).-gzipresidual NUMBERโ Add extra gzip bits. You should never need it.-gzipbody STRINGโ GzipSTRINGand send it as body.-gziplen NUMBERโ Combine-bodylenand-gzipbody: generate a string of lengthNUMBER, gzip it and send as body.
write_body STRING
Write the body of a request or a response to a file. By using the shell command, higher-level checks on the body can be performed (eg. XML, JSON, ...) provided that such checks can be delegated to an external program.
delay ๐ธ
NOTE: This command is available everywhere commands are given.
Sleep for the number of seconds specified in the argument. The number can include a fractional part, e.g. 1.5.
feature ๐ง
Test that the required feature(s) for a test are available, and skip the test otherwise; or change the interpretation of the test, as documented below. feature takes any number of arguments from this list:
64bitโ The environment is 64 bitsipv4โ127.0.0.1worksipv6โ[::1]worksdnsโ DNS lookups are workingtopbuildโ The test has been started with-irootโ The test has been invoked by the root useruser <username>โ The<username>user is presentgroup <groupname>โ The<groupname>group is presentuser_varnishโ The varnish user is presentuser_vinylโ The vinyl user is presentuser_vcacheโ The vcache user is presentgroup_varnishโ The varnish group is presentgroup_vinylโ The vinyl group is presentcmd <command-line>โ A command line that should execute with a zero exit statusignore_unknown_macroโ Do not fail the test if a string of the form${...}is not recognized as a macro.persistent_storageโ Varnish was built with the deprecated persistent storage.coverageโ Varnish was built with code coverage enabled.asanโ Varnish was built with the address sanitizer.msanโ Varnish was built with the memory sanitizer.tsanโ Varnish was built with the thread sanitizer.ubsanโ Varnish was built with the undefined behavior sanitizer.sanitizerโ Varnish was built with a sanitizer.workspace_emulatorโ Varnish was built with its workspace emulator.abstract_udsโ Creation of an abstract unix domain socket succeeded.disable_aslrโ ASLR can be disabled.vtest_cmd <command>โ vtest command<command>is available
A feature name can be prefixed with an exclamation mark (!) to skip a test if the feature is present.
Be careful withignore_unknown_macro, because it may cause a test with a misspelled macro to fail silently. You should only need it if you must run a test with strings of the form"${...}". ๐ซถ
filewrite
Write strings to file
filewrite [-a] /somefile "Hello" " " "Worldn"
The -a flag opens the file in append mode.
haproxy ๐ฅ
Define and interact with haproxy instances.
To define an haproxy server, you'll use this syntax:
haproxy hNAME -conf-OK CONFIG
haproxy hNAME -conf-BAD ERROR CONFIG
haproxy hNAME [-D] [-W] [-arg STRING] [-conf[+vcl] STRING]
The first haproxy hNAME invocation will start the haproxy master process in the background, waiting for the -start switch to actually start the child.
Arguments:
hNAMEโ Identify the HAProxy server with a string, it must starts with'h'.-conf-OK CONFIGโ Run haproxy in'-c'mode to check config is OK.stdout/stderrshould contain'Configuration file is valid'. The exit code should be0.-conf-BAD ERROR CONFIGโ Run haproxy in'-c'mode to check config is BAD."ERROR"should be part of the diagnostics onstdout/stderr. The exit code should be1.-Dโ Run HAproxy in daemon mode. If not given'-d'mode used.-Wโ Enable HAproxy in Worker mode.-Sโ Enable HAproxy Master CLI in Worker mode-arg STRINGโ Pass an argument to haproxy, for example"-h simple_list".-cli STRINGโ Specify the spec to be run by the command line interface (CLI).-mcli STRINGโ Specify the spec to be run by the command line interface (CLI) of the Master process.-conf STRINGโ Specify the configuration to be loaded by this HAProxy instance.-conf+backend STRINGโ Specify the configuration to be loaded by this HAProxy instance, all server instances will be automatically appended-startโ Start this HAProxy instance.-waitโ Stop this HAProxy instance.-expectexit NUMBERโ Expect haproxy to exit(3) with this value
haproxy CLI Specification ๐
expect OP STRINGโ Regex match the CLI reception buffer withSTRINGif OP is~or, on the contrary, if OP is!~check that there is no regex match.send STRINGโ PushSTRINGon the CLI connection.STRINGwill be terminated by an end of line character (n).
include ๐
Executes a vtc fragment:
include FILE [...]
Open a file and execute it as a VTC fragment. This command is available everywhere commands are given.
logexpect ๐
Reads the VSL and looks for records matching a given specification. It will process records trying to match the first pattern, and when done, will continue processing, trying to match the following pattern. If a pattern isn't matched, the test will fail.
logexpect threads are declared this way:
logexpect lNAME -v <id> [-g <grouping>] [-d 0|1] [-q query] \
[vsl arguments] {
expect <skip> <vxid> <tag> <regex>
expect <skip> <vxid> <tag> <regex>
fail add <vxid> <tag> <regex>
fail clear
abort
...
} [-start|-wait|-run]
And once declared, you can start them, or wait on them:
logexpect lNAME <-start|-wait>
With:
lNAMEโ Name the logexpect thread, it must start with'l'.-v idโ Specify the varnish instance to use (most of the time,id=v1).-g <session|request|vxid|rawโ Decide how records are grouped, see-gin man varnishlog for more information.-d <0|1>โ Start processing log records at the head of the log instead of the tail.-q queryโ Filter records using a query expression, see man vsl-query for more information. Multiple-qoptions are not supported.-mโ Also emit log records for misses (only for debugging)-errโ Invert the meaning of success. Usually called once to expect the logexpect to fail-startโ Start the logexpect thread in the background.-waitโ Wait for the logexpect thread to finish-runโ Equivalent to"-start -wait".
VSL arguments (similar to the varnishlog options):
-Cโ Use caseless regex. no case sensitivity, bestie ๐-i <taglist>โ Include tags. taglist goes here ๐ท๏ธ-I <[taglist:]regex>โ Include by regex. regex girlies rise โจ-T <seconds>โ Transaction end timeout. clock's ticking โณ
๐ธ expect specification ๐ธ
skip: [uint|*|?]โ Max number of record to skip. uint, star, or question mark ๐vxid: [uint|*|=]โ vxid to match. match that vxid ๐tag: [tagname|*|=]โ Tag to match against. tag, you're it ๐ท๏ธregex:โ regular expression to match against (optional). optional but iconic ๐
For skip, vxid and tag, '*' matches anything, '=' expects the value of the previous matched record. The '?' marker is equivalent to zero, expecting a match on the next record. The difference is that '?' can be used when the order of individual consecutive logs is not deterministic. In other words, lines from a block of alternatives marked by '?' can be matched in any order, but all need to match eventually. periodt ๐
๐ fail specification ๐
add:โ Add to the fail list. Arguments are equivalent to expect, except for skip missing. no skip, just vibes ๐ญclear:โ Clear the fail list. clean slate ๐งผ
Any number of fail specifications can be active during execution of a logexpect. All active fail specifications are matched against every log line and, if any match, the logexpect fails immediately. immediately, bestie ๐ฅ
For a logexpect to end successfully, there must be no specs on the fail list, so logexpects should always end with
expect <skip> <vxid> <tag> <termination-condition> fail clear
write that down ๐
๐ญ abort specification ๐ญ
abort(3) varnishtest, intended to help debugging of the VSL client library itself. debugging era ๐
๐ loop ๐
loop NUMBER STRINGโ ProcessSTRINGas a specification,NUMBERtimes. loop it and loop it again ๐
This works inside all specification strings. everywhere, no cap โจ
๐ฅ๏ธ process ๐ฅ๏ธ
Run a process with stdin+stdout on a pseudo-terminal and stderr on a pipe. pseudo-terminal realness ๐
Output from the pseudo-terminal is copied verbatim to ${pNAME_out}, and the -log/-dump/-hexdump flags will also put it in the vtc-log. verbatim, fr fr ๐
The pseudo-terminal is not in ECHO mode, but if the programs run set it to ECHO mode ("stty sane") any input sent to the process will also appear in this stream because of the ECHO. echo echo echo ๐ข
Output from the stderr-pipe is copied verbatim to ${pNAME_err}, and is always included in the vtc_log. always, bestie ๐
process pNAME SPEC [-allow-core] [-expect-exit N] [-expect-signal N]
[-dump] [-hexdump] [-log] [-run] [-close] [-kill SIGNAL]
[-start] [-stop] [-wait] [-write STRING] [-writeln STRING]
[-writehex HEXSTRING] [-need-bytes [+]NUMBER] [-screen-dump]
[-winsz LINES COLUMNS] [-ansi-response] [-expect-cursor LINE
COLUMN] [-expect-text LINE COLUMN TEXT] [-match-text LINE
COLUMN REGEXP]
pNAMEโ Name of the process. It must start with'p'. p for process, p for periodt ๐SPECโ The command(s) to run in this process. do the thing ๐ฅ-hexdumpโ Log output withvtc_hexdump(). Must be before-start/-run. before, bestie โ ๏ธ-dumpโ Log output withvtc_dump(). Must be before-start/-run. before, again โ ๏ธ-logโ Log output withVLU/vtc_log(). Must be before-start/-run. before, always โ ๏ธ-startโ Start the process. go go go ๐-expect-exit Nโ Expect exit statusN. exit code check โ-expect-signal Nโ Expect signal in exit statusN. signal check ๐ถ-allow-coreโ Core dump in exit status is OK. core dump? allowed, slay ๐-waitโ Wait for the process to finish. patience, babe โณ-runโ Shorthand for-start -wait. start and wait, together ๐ซถ
In most cases, if you just want to start a process and wait for it to finish, you can use the shell command instead. The following commands are equivalent:
shell "do --something"
process p1 "do --something" -run
However, you may use the process variant to conveniently collect the standard input and output without dealing with shell redirections yourself. The shell command can also expect an expression from either output, consider using it if you only need to match one. iykyk ๐
-key KEYSYMโ Send emulated key-press.KEYSYMcan be one of (NPAGE,PPAGE,HOME,END). press that key โจ๏ธ-kill SIGNALโ Send a signal to the process. The argument can be either the string"TERM","INT", or"KILL"for SIGTERM, SIGINT or SIGKILL signals, respectively, or a hyphen (-) followed by the signal number. kill it, respectfully ๐
If you need to use other signal names, you can use the kill(1) command directly:
shell "kill -USR1 ${pNAME_pid}"
Note that SIGHUP usage is discouraged in test cases. don't do it, bestie ๐
-stopโ Shorthand for-kill TERM. stop right there โ-closeโ Alias for"-kill HUP". close but not really ๐-winsz LINES COLUMNSโ Change the terminal window size toLINlines andCOLcolumns. resize, queen ๐-write STRINGโ Write a string to the process' stdin. write it out โ๏ธ-writeln STRINGโ Same as-writefollowed by a newline (\n). newline energy ๐-writehex HEXSTRINGโ Same as-writebut interpreted as hexadecimal bytes. hex bytes, fancy ๐ข-need-bytes [+]NUMBERโ Wait until at leastNUMBERbytes have been received in total. If+is prefixed,NUMBERnew bytes must be received. count those bytes ๐งฎ-ansi-responseโ Respond to terminal respond-back sequences. ansi, she responds ๐ฌ-expect-cursor LINE COLUMNโ Expect cursors location. where's the cursor? ๐-expect-text LINE COLUMNS TEXTโ Wait forTEXTto appear atLIN,COLon the virtual screen. Lines and columns are numbered1...NLIN==0means "on any line"COL==0means "anywhere on the line". text hunt ๐-match-text LINE COLUMN REGEXPโ Wait for thePATregular expression to match the text atLIN,COLon the virtual screen. Lines and columns are numbered1...NLIN==0means "on any line"COL==0means "anywhere on the line". regex match, slay โจ-screen-dumpโ Dump the virtual screen intovtc_log. dump it ๐ธ
๐ฑ setenv ๐ฑ
Set or change an environment variable:
setenv FOO "bar baz"
The above will set the environment variable $FOO to the value provided. There is also an -ifunset argument which will only set the value if the environment variable does not already exist:
setenv -ifunset FOO quux
environmental queen ๐
๐ shell ๐
NOTE: This command is available everywhere commands are given. everywhere, periodt ๐
Pass the string given as argument to a shell. If you have multiple commands to run, you can use curly brackets to describe a multi-lines script, eg:
shell {
echo begin
cat /etc/fstab
echo end
}
By default a zero exit code is expected, otherwise the vtc will fail. zero or fail, no in-between ๐ญ
Notice that the commandstring is prefixed with "exec 2>&1;" to combine stderr and stdout back to the test process. stderr + stdout = besties ๐ซถ
Optional arguments:
-errโ Expect non-zero exit code. error? expected ๐-exit Nโ Expect exit codeNinstead of zero. custom exit code โ-expect STRINGโ Expect string to be found in stdout+err. look for the string ๐-match REGEXPโ Expect regexp to match the stdout+err output. regexp match โจ
๐ stream ๐
(note: this section is at the top-level for easier navigation, but it's part of the client/server specification) iykyk ๐
Streams map roughly to a request in HTTP/2, a request is sent on stream N, the response too, then the stream is discarded. The main exception is the first stream, 0, that serves as coordinator. stream 0 is the coordinator, no cap ๐
Stream syntax follow the client/server one:
stream ID [SPEC] [ACTION]
ID is the HTTP/2 stream number, while SPEC describes what will be done in that stream. If ID has the value next, the actual stream number is computed based on the last one. next means next โก๏ธ
Note that, when parsing a stream action, if the entity isn't operating in HTTP/2 mode, these spec is ran before:
txpri/rxpri # client/server
stream 0 {
txsettings
rxsettings
txsettings -ack
rxsettings
expect settings.ack == true
} -run
And HTTP/2 mode is then activated before parsing the specification. activated, slay โก
๐ฌ Actions ๐ฌ
-startโ Run the specification in a thread, giving back control immediately. thread go brrr ๐งต-waitโ Wait for the started thread to finish running the spec. wait for it โณ-runโ equivalent to calling-startthen-wait. start + wait = run ๐ซถ
๐ Specification ๐
The specification of a stream follows the exact same rules as one for a client or a server. same rules, same vibe โจ
โ๏ธ txreq, txresp, txcont, txpush โ๏ธ
These four commands are about sending headers. txreq and txresp will send HEADER frames; txcont will send CONTINUATION frames; txpush PUSH frames. The only difference between txreq and txresp are the default headers set by each of them. headers, but make it fashion ๐
-noaddโ Do not add default headers. Useful to avoid duplicates when sending default headers using-hdr,-idxHdrand-litIdxHdr. no duplicates, bestie ๐ซ-status INT(txresp) โ Set the:statuspseudo-header. status check ๐-url STRING(txreq,txpush) โ Set the:pathpseudo-header. path, but make it URL ๐-method STRING(txreq,txpush) โ Set the:methodpseudo-header. method to the madness ๐ ๏ธ-req STRING(txreq,txpush) โ Alias for-method. same thing, different name ๐-scheme STRING(txreq,txpush) โ Set the:schemepseudo-header. scheme team ๐-hdr STRING1 STRING2โ Insert a header,STRING1being the name, andSTRING2the value. name + value = header โจ-idxHdr INTโ Insert an indexed header, usingINTas index. index it ๐ข-litIdxHdr inc|not|never INT huf|plain STRINGโ Insert an literal, indexed header. The first argument specify if the header should be added to the table, shouldn't, or mustn't be compressed if/when retransmitted.
INT is the index of the header name to use.
The third argument informs about the Huffman encoding: yes (huf) or no (plain).
The last term is the literal value of the header. literal queen ๐
-litHdr inc|not|never huf|plain STRING1 huf|plain STRING2โ Insert a literal header, with the same first argument as-litIdxHdr.
The second and third terms tell what the name of the header is and if it should be Huffman-encoded, while the last two do the same regarding the value. huffman encoding, fancy ๐ธ
-body STRING(txreq,txresp) โ Specify a body, effectively puttingSTRINGinto a DATA frame after the HEADER frame is sent. body after headers ๐ฆ-bodyfrom FILE(txreq,txresp) โ Same as-bodybut content is read fromFILE. read from file ๐-bodylen INT(txreq,txresp) โ Do the same thing as-bodybut generate a string ofINTlength for you. auto-generate the body ๐ค-gzipbody STRING(txreq,txresp) โ GzipSTRINGand send it as body. gzip it and ship it ๐๏ธ-gziplen NUMBER(txreq,txresp) โ Combine-bodylenand-gzipbody: generate a string of lengthNUMBER, gzip it and send as body. combo move ๐ฅ-nostrend(txreq,txresp) โ Don't set theEND_STREAMflag automatically, making the peer expect a body after the headers. no end stream ๐ซ-nohdrendโ Don't set theEND_HEADERSflag automatically, making the peer expect more HEADER frames. no end headers ๐ซ-dep INT(txreq,txresp) โ Tell the peer that this content depends on the stream with theINTid. dependency drama ๐-ex(txreq,txresp) โ Make the dependency exclusive (-depis still needed). exclusive, but need dep ๐-weight(txreq,txresp) โ Set the weight for the dependency. weight it โ๏ธ-promised INT(txpush) โ The id of the promised stream. promised stream, iconic ๐-pad STRING/-padlen INTโ ok so this one adds padding to the frame ๐ either the exact string you handed over with-pad, or one it generates for you โ and in the-padlencase that generated string isINTbytes long. Cute, no notes.
โจ txdata
By default, data frames are empty. The receiving end still knows the whole body landed because of the END_STREAM flag that gets set on the last DATA frame โ and txdata sets it for you automatically, bestie. It just does it. ๐
-data STRINGโ the data to be embedded into the frame.-datalen INTโ generate a stringINTbytes long and send it in the frame.-pad STRING/-padlen INTโ same deal as ever: add a padding string to the frame, either the one you gave with-pad, or one generated for you,INTbytes long in the-padlencase.-nostrendโ don't set the END_STREAM flag, which lets you send more data on this stream. Live your truth.
๐ธ rxreq, rxresp
These two are the convenience duo โ they receive the headers and the body of an incoming request or response in one go. The only difference between them is that rxreq can only be used by a server, and rxresp only by a client. That's it. That's the whole plot.
๐ฆ rxhdrs
rxhdrs expects one HEADER frame, and then โ depending on the arguments โ zero or more CONTINUATION frames.
-allโ keep waiting for CONTINUATION frames until the END_HEADERS flag is seen.-some INTโ retrieveINT- 1 CONTINUATION frames after the HEADER frame.
๐ rxpush
Works exactly like rxhdrs, except it expects a PUSH frame and then zero or more CONTINUATION frames. Same energy, different frame.
-allโ keep waiting for CONTINUATION frames until the END_HEADERS flag is seen.-some INTโ retrieveINT- 1 CONTINUATION frames after the PUSH frame.
๐ rxdata
Receiving data happens through the rxdata keywords, and it'll retrieve one DATA frame. Want more than one? Use these two convenience arguments, bestie:
-allโ keep waiting for DATA frames until one sets the END_STREAM flag.-some INTโ retrieveINTDATA frames.
Receive a frame, any frame. ๐ (yes that's the whole vibe)
๐ง sendhex
Push bytes straight onto the wire. sendhex takes exactly one argument: a string describing the bytes in hex notation, with possible whitespaces between them. Obsessed with the simplicity. Here's an example:
sendhex "00 00 08 00 0900 8d"
๐ rxgoaway
Receive a GOAWAY frame. Short king.
๐ txgoaway
Possible options include:
-err STRING|INTโ set the error code to explain the termination. That second argument can be an integer, or the string version of the error code as found inrfc7540#7.-laststream INTโ the id of the "highest-numbered stream identifier for which the sender of the GOAWAY frame might have taken some action on or might yet take action on". (Yes, that's the quote. It's giving RFC.)-debugโ specify the debug data, if any, to append to the frame.
๐ซถ gunzip
Same as the gunzip command for HTTP/1.
โญ rxping
Receive a PING frame.
๐ txping
Send a PING frame.
-data STRINGโ specify the payload of the frame, withSTRINGbeing an 8-char string.-ackโ set the ACK flag.
๐ฅ rxprio
Receive a PRIORITY frame.
๐ txprio
Send a PRIORITY frame.
-stream INTโ indicate the id of the stream the sender stream depends on.-exโ the dependency should be made exclusive (only this stream depends on the parent stream).-weight INTโ an 8-bit integer, used to balance priority between streams that depend on the same streams.
๐ญ rxrst
Receive a RST_STREAM frame.
๐ txrst
Send a RST_STREAM frame. By default, txrst sends a 0 error code (NO_ERROR). Chill of it.
-err STRING|INTโ sets the error code to be sent. The argument can be an integer, or a string describing the error such asNO_ERROR, orCANCEL(seerfc7540#11.4for more strings).
๐ฆ rxsettings
Receive a SETTINGS frame.
๐ธ txsettings
SETTINGS frames must be acknowledged, and the arguments are as follow (most of them are straight from rfc7540#6.5.2):
-hdrtbl INTโ headers table size-push BOOLโ whether push frames are accepted or not-maxstreams INTโ maximum concurrent streams allowed-winsize INTโ sender's initial window size-framesize INTโ largest frame size authorized-hdrsize INTโ maximum size of the header list authorized-0xHH[HH] INTโ tx arbitrary settings with tagxx-ackโ set the ack bit
๐ rxwinup
Receive a WINDOW_UPDATE frame.
๐ txwinup
Transmit a WINDOW_UPDATE frame, increasing the amount of credit of the connection (from stream 0) or of the stream (any other stream).
-size INTโ giveINTcredits to the peer.
write_body STRING โ same as the write_body command for HTTP/1.
๐ง expect
expect inside a stream works just like it does in a client or server, except the elements being compared are different. Most of these elements are frame specific โ meaning the last frame received on that stream must be of the correct type. Here's the list of keywords you can look at ๐
โญ GOAWAY specific
goaway.errโ the error code (as an integer) of the GOAWAY frame.goaway.laststreamโ Last-Stream-IDgoaway.debugโ debug data, if any.
๐ PING specific
ping.dataโ the 8-bytes string of the PING frame payload.ping.ack (PING)โ"true"if the ACK flag was set,"false"otherwise.
๐ฅ PRIORITY specific
prio.streamโ the stream ID announced.prio.exclusiveโ"true"if the priority is exclusive, else"false".prio.weightโ the dependency weight.
๐ PUSH_PROMISE specific
push.idโ the id of the promised stream.
๐ญ RESET_STREAM specific
rst.errโ the error code (as an integer) of the RESET_STREAM frame.
๐ SETTINGS specific
settings.ackโ"true"if the ACK flag was set, else"false".settings.pushโ"true"if the push settings was set to yes,"false"if set to no, and<undef>if not present.settings.hdrtblโ value of HEADER_TABLE_SIZE if set,<undef>otherwise.settings.maxstreamsโ value of MAX_CONCURRENT_STREAMS if set,<undef>otherwise.settings.winsizeโ value of INITIAL_WINDOW_SIZE if set,<undef>otherwise.setting.framesizeโ value of MAX_FRAME_SIZE if set,<undef>otherwise.settings.hdrsizeโ value of MAX_HEADER_LIST_SIZE if set,<undef>otherwise.
๐ฆ WINDOW_UPDATE specific
winup.sizeโ the size of the upgrade given by the WINDOW_UPDATE frame.
๐ Generic frame
frame.dataโ payload of the last frameframe.typeโ type of the frame, as integer.frame.sizeโ size of the frame.frame.streamโ stream of the frame (corresponds to the one you're executing this from, obviously).frame.padding(for DATA, HEADERS, PUSH_PROMISE frames) โ number of padded bytes.
๐ซถ Request and response
Note: it's possible to inspect a request or response while it is still being constructed (in-between two frames, for example).
req.bodylen/resp.bodylenโ length in bytes of the request/response so far.req.body/resp.bodyโ body of the request/response so far.req.http.STRING/resp.http.STRINGโ value of the headerSTRINGin the request/response.req.status/resp.statusโ:statuspseudo-header's value.req.url/resp.urlโ:pathpseudo-header's value.req.method/resp.methodโ:methodpseudo-header's value.req.authority/resp.authorityโ:methodpseudo-header's value.req.scheme/resp.schemeโ:methodpseudo-header's value.
โญ Stream
stream.windowโ the current local window size of the stream, or, if on stream 0, of the connection.stream.peer_windowโ the current peer window size of the stream, or, if on stream 0, of the connection.stream.weightโ weight of the streamstream.dependencyโ id of the stream this one depends on.
๐ Index tables
tbl.dec.size/tbl.enc.sizeโ size (bytes) of the decoding/encoding table.tbl.dec.size/tbl.enc.maxsizeโ maximum size (bytes) of the decoding/encoding table.tbl.dec.length/tbl.enc.lengthโ number of headers in decoding/encoding table.tbl.dec[INT].key/tbl.enc[INT].keyโ name of the header at indexINTof the decoding/encoding table.tbl.dec[INT].value/tbl.enc[INT].valueโ value of the header at indexINTof the decoding/encoding table.
๐ syslog
Define and interact with syslog instances (for use with haproxy).
To define a syslog server, you'll use this syntax:
syslog SNAME
Arguments:
SNAMEโ identify the syslog server with a string which must start with'S'.-level STRINGโ set the default syslog priority level used by any subsequent"recv"command. Any syslog dgram with a different level will be skipped by the"recv"command. This default level value may be superseded by the"recv"command if supplied as first argument:"recv <level>".-startโ start the syslog server thread in the background.-repeatโ instead of processing the specification only once, do itNUMBERtimes.-bindโ bind the syslog socket to a local address.-waitโ wait for that thread to terminate.-stopโ stop the syslog server thread.
๐ฆ tunnel
The goal of a tunnel is to help control the data transfer between two parties โ for example, to trigger socket timeouts in the middle of protocol frames, without needing to change how both parties are implemented. Big brain move, honestly.
A tunnel accepts a connection and then connects on behalf of the source to the desired destination. Once both connections are established, the tunnel transfers bytes unchanged between the source and destination. That transfer can be interrupted, usually with the help of synchronization methods like barriers. Once the transfer is paused, it's possible to let a specific amount of bytes move in either direction. Chefs kiss.
โจ Arguments
-startโ start the tunnel in background, processing the last given specification.-start+pauseโ start the tunnel, but already paused.-waitโ block until the thread finishes.-listen STRINGโ dictate the listening socket for the server.STRINGis of the form"IP PORT", or"HOST PORT".
Listens by default to a local random port.
-connect STRINGโ indicate the server to connect to.STRINGis also of the form"IP PORT", or"HOST PORT".
Connects by default to a varnish instance called v1.
๐ธ Specification
The specification contains a list of tunnel commands that can be combined with barriers and delays. For example:
tunnel t1 {
barrier b1 sync
pause
delay 1
send 42
barrier b2 sync
resume
} -start
okay so if one end of the tunnel gets closed before the spec is over, bestie, the whole test case just fails. ๐ and if a spec ends while it's still paused, the tunnel gets implicitly resumed. no cap.
pause ๐ธ
Wait for all the in-flight bytes to actually transfer, then pause the tunnel. ๐
the tunnel has to be running for this to work. don't come at it paused, it's not the vibe.
recv NUMBER ๐ฆ
Wait until NUMBER bytes make it from destination to source.
tunnel must be paused, and it stays paused after. ๐ซถ
resume โจ
Resume moving bytes in both directions.
tunnel must be paused first, bestie.
send NUMBER ๐
Wait until NUMBER bytes go from source to destination.
tunnel must be paused, and yeah it remains paused afterwards. ๐
varnish ๐
Define varnish instances and talk to them. the main character section fr.
To define a Varnish server, you use this syntax:
varnish vNAME [-arg STRING] [-vcl STRING] [-vcl+backend STRING]
[-errvcl STRING STRING] [-jail STRING] [-proto PROXY]
The very first varnish vNAME invocation kicks off the varnishd master process in the background, and it just... waits there for the -start switch to actually start the child. iconic behaviour tbh.
Types used in the description below:
PATTERN โ is a 'glob' style pattern (ie: fnmatch(3)) as used in shell filename expansion. yes bestie, like your shell does. ๐
Arguments:
vNAMEโ identify the Varnish server with a string, and it must start with'v'. rules are rules. ๐-arg STRINGโ pass an argument straight tovarnishd, for example"-h simple_list".
and listen: if the ${varnishd_args_prepend} or ${varnishd_args_append} macros are defined, they get expanded and inserted before / appended to the varnishd command line as constructed by varnishtest, before the command line itself is expanded. this is the little cheat code that lets you tweak the varnishd command line without editing test cases. you define these macros with the -D option for varnishtest. iykyk. ๐ฅ
-vcl STRINGโ specify the VCL to load on this Varnish instance. you're probably gonna want multi-line strings for this one, like({...}). ๐-vcl+backend STRINGโ does the exact same thing as-vcl, but it also adds the definition block of known backends (ie. the ones already defined). slay.-errvcl STRING1 STRING2โ loadsSTRING2as VCL, expects it to fail, and expects Varnish to send an error string matchingSTRING1. ๐-jail STRINGโ go look atman varnishd(-j) for more info, bestie.-proto PROXYโ makes Varnish use the proxy protocol. note thatPROXYhere is the actual string. not a placeholder, the literal vibe.
You can also decide to start the Varnish instance and/or wait for a bunch of events:
varnish vNAME [-start] [-wait] [-wait-running] [-wait-stopped]
-startโ start the child process. ๐
once it's successfully started, these macros become available for the default listen address: ${vNAME_addr}, ${vNAME_port} and ${vNAME_sock}. and there's more โ including the listen address name for each address vNAME listens to, like for example: ${vNAME_a0_addr}. so many macros, we love to see it. โจ
-stopโ stop the child process.-syntaxโ set the VCL syntax level for this command (default:4.1). ๐-waitโ wait for that instance to terminate.-wait-runningโ wait for the Varnish child process to be started.-wait-stoppedโ wait for the Varnish child process to stop.-cleanupโ once Varnish is stopped, clean everything after it. heads up: this is only used in very few tests and you should never need it. like, seriously.-expectexit NUMBERโ expectvarnishdtoexit(3)with this value.
Once Varnish is started, you can talk to it โ exactly like you would through varnishadm โ with these extra switches:
varnish vNAME [-cli STRING] [-cliok STRING] [-clierr STRING]
[-clijson STRING]
-cli STRING|-cliok STRING|-clierr STATUS STRING|-cliexpect [!] REGEXP STRINGโ all four of these sendSTRINGto the CLI. the only difference is what they expect back.-clidoesn't expect anything at all (chill),-cliokexpects200,-clierrexpectsSTATUS, and-cliexpectexpects theREGEXPto match the returned response. and-cliexpect ! ...negates the match. ๐-clijson STRINGโ sendSTRINGto the CLI, expect success (CLIS_OK/200) and check that the response is parsable JSON. โ
You can also interact with its shared memory โ like you would through tools such as varnishstat โ with these additional switches:
-expect !PATTERN|PATTERN OP NUMBER|PATTERN OP PATTERNโ peek into the VSM and make sure the first VSC counter identified byPATTERNhas a correct value.OPcan be==,>,>=,<,<=. for example:
varnish v1 -expect SM?.s1.g_space > 1000000
varnish v1 -expect cache_hit >= cache_hit_grace
in the ! form, the test fails if a counter matches PATTERN. ๐
the MAIN. namespace can be omitted from PATTERN. ๐ซถ
and the test takes up to 5 seconds before timing out. so don't hold your breath, bestie.
-vsc PATTERNโ dump VSC counters matchingPATTERN.-vsl_catchupโ wait until the logging thread has idled, so you know all the generated log is flushed. ๐
varnishtest ๐ง
alternate name for 'vtest', see above. โจ
vsm ๐ฆ
interact with the shared memory of a varnish instance.
to define a VSM consumer, use this syntax:
vsm mNAME [-n STRING]
Arguments:
mNAMEโ identify the VSM consumer, and it must start with'm'. ๐-n STRINGโ choose the working directory of the varnish instance. by default a VSM consumer connects to${v1_name}.-attachโ attach to a new varnish instance. it implicitly detaches from the current varnish instance if that applies. ๐ซถ-detachโ detach from the current varnish instance.-expect-status STRINGโ check that the status of the VSM matches the list of status flags fromSTRING. the expected status is written as a comma-separated list of flags, and the list inSTRINGis not sensitive to the order of the flags. order? doesn't matter, we don't gatekeep. ๐
the available flags are:
mgt-runningmgt-changedmgt-restartedwrk-runningwrk-changedwrk-restarted
expecting a status automatically attaches to the varnish instance if that wasn't already the case. no cap. โจ
vtest ๐ธ
this should be the first command in your vtc, because it identifies the test case with a short but descriptive sentence. it takes exactly one argument, a string, eg:
vtest "Check that vtest is actually a valid command"
and yeah, it also prints that string in the log. ๐
๐ HISTORY
this document has been written by Guillaume Quintard. thank you legend. ๐
๐ SEE ALSO
๐ COPYRIGHT
this document is licensed under the same licence as Varnish itself. see LICENCE for details.
- Copyright (c) 2006-2016 Varnish Software AS
and that's a wrap on VTC(7), bestie!! ๐๐ฆโจ you read the whole thing, that's genuinely iconic of you. go forth and write some unhinged test cases. mwah. ๐
๐
(psst โ this was the VTC(7) page from the Varnish source tree, dated 2006-2016 territory, brought to you by Varnish Software AS. okay bye!! ๐)
๐ฆ where this page comes from
- fedora 44: varnish 8.0.2-1.fc44 ยท BSD-2-Clause AND (BSD-2-Clause-FreeBSD AND BSD-3-Clause AND LicenseRef-Fedora-Public-Domain AND Zlib)
- fedora 44: varnish 8.0.2-1.fc44 ยท BSD-2-Clause AND (BSD-2-Clause-FreeBSD AND BSD-3-Clause AND LicenseRef-Fedora-Public-Domain AND Zlib)
- ๐ the page's license: BSD-2-Clause
- Copyright (c) 2016-2017 Varnish Software AS
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms