.\" -*-nroff-*- .\" .\" Manual for the test vector framework .\" .\" (c) 2024 Straylight/Edgeware .\" . .\"----- Licensing notice --------------------------------------------------- .\" .\" This file is part of the mLib utilities library. .\" .\" mLib is free software: you can redistribute it and/or modify it under .\" the terms of the GNU Library General Public License as published by .\" the Free Software Foundation; either version 2 of the License, or (at .\" your option) any later version. .\" .\" mLib is distributed in the hope that it will be useful, but WITHOUT .\" ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or .\" FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public .\" License for more details. .\" .\" You should have received a copy of the GNU Library General Public .\" License along with mLib. If not, write to the Free Software .\" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, .\" USA. . .\"-------------------------------------------------------------------------- .so ../defs.man \" @@@PRE@@@ . .\"-------------------------------------------------------------------------- .TH tvec 3mLib "11 March 2024" "Straylight/Edgeware" "mLib utilities library" .\" TVMISC_PTR .\" TVMISC_INT .\" TVMISC_UINT .\" TVMISC_FLT .\" TVMISC_LIMIT . .\" @TVEC_REGSLOTS .\" @TVRF_UNSET .\" @TVRF_OPT .\" @TVRF_ID .\" @TVEC_ENDREGS . .\" @tvec_begin .\" @tvec_end .\" @tvec_read .\" @tvec_humanoutput .\" @tvec_tapoutput .\" @tvec_dfltoutput . .\"-------------------------------------------------------------------------- .SH NAME tvec \- test vector framework . .\"-------------------------------------------------------------------------- .SH SYNOPSIS .nf .B "#include " .PP .ta 2n .B "union tvec_misc {" .B " const void *p;" .B " long i;" .B " unsigned long u;" .B " double f;" .B "};" .B "enum {" .B " TVMISC_PTR," .B " TVMISC_INT," .B " TVMISC_UINT," .B " TVMISC_FLT," .B " ...," .B " TVMISC_LIMIT," .B "};" .PP .ta 2n +2n .B "union tvec_regval {" .B " long i;" .B " unsigned long u;" .B " void *p;" .B " double f;" .B " struct { char *p; size_t sz; } text;" .B " struct { unsigned char *p; size_t sz; } bytes;" .B " struct {" .B " unsigned char *p; size_t sz;" .B " size_t a, m;" .B " size_t off;" .B " } buf;" .B " TVEC_REGSLOTS" .B "};" .B "struct tvec_reg {" .B " unsigned f;" .B " union tvec_regval v;" .B "};" .B "#define TVRF_SEEN ..." .B "#define TVRF_LIVE ..." .PP .ta 2n .B "struct tvec_regdef {" .B " const char *name;" .B " const struct tvec_regty *ty;" .B " unsigned i;" .B " unsigned f;" .B " union tvec_misc arg;" .B "};" .B "#define TVRF_UNSET ..." .B "#define TVRF_OPT ..." .B "#define TVRF_SYNTH ..." .B "#define TVRF_ID ..." .B "#define TVEC_ENDREGS ..." .PP .B "struct tvec_state;" .PP .B "struct tvec_env;" .ta \w'\fBtypedef void tvec_testfn('u .BI "typedef void tvec_testfn(const struct tvec_reg *" in , .BI " struct tvec_reg *" out , .BI " void *" ctx ); .ta 2n .B "struct tvec_test {" .B " const char *name;" .B " const struct tvec_regdef *regs;" .B " const struct tvec_env *env;" .B " tvec_testfn *fn;" .B "};" .PP .ta 2n .B "struct tvec_config {" .B " const struct tvec_test *const *tests;" .B " unsigned nrout, nreg;" .B " size_t regsz;" .B "};" .B "struct tvec_output;" .PP .ta \w'\fBvoid tvec_begin('u .BI "void tvec_begin(struct tvec_state *" tv_out , .BI " const struct tvec_config *" config , .BI " struct tvec_output *" o ); .BI "int tvec_end(struct tvec_state *" tv ); .BI "int tvec_read(struct tvec_state *" tv ", const char *" infile ", FILE *" fp ); .PP .B "#define TVHF_TTY ..." .B "#define TVHF_COLOUR ..." .ta \w'\fBstruct tvec_output *tvec_humanoutput('u .BI "struct tvec_output *tvec_humanoutput(FILE *" fp , .BI " unsigned " f ", unsigned " m ); .BI "struct tvec_output *tvec_machineoutput(FILE *" fp ); .BI "struct tvec_output *tvec_tapoutput(FILE *" fp ); .BI "struct tvec_output *tvec_dfltoutput(FILE *" fp ); .PP .ta 2n .B "struct tvec_amargs {" .B " unsigned f;" .B " const char *name;" .B " FILE *log;" .B " FILE *trs;" .B "};" .BI "struct tvec_output *tvec_amoutput(const struct tvec_amargs *" a ); .fi . .\"-------------------------------------------------------------------------- .SH DESCRIPTION . The .B header file provides definitions and declarations for the core of mLib's .IR "test vector framework" . .PP The test vector framework is rather large and complicated, so the documentation for it is split into multiple manual pages. This one provides a conceptual overview and describes the essentials for using it to build simple tests. . .SS Conceptual overview A .I "test session" runs a collection of tests and reports on the outcome. .PP A .I test involves exercising some functionality and checking that it behaves properly. A test can have four .IR outcomes . It can .IR pass : the functionality behaved properly. It can .IR fail : the functionality did not behave properly. It can experience an .IR "expected failure" : the functionality behaved as expected, but the expected behaviour is known to be incorrect. Or it can be .IR skipped : for some reason, the test couldn't be performed. .PP Tests are gathered together into .IR "test groups" . Each test group has a name. Like a individual tests, test groups also have outcomes: they can pass, fail, or be skipped. A test group cannot experience expected failure. .PP A session may also encounter .IR errors , e.g., as a result of malformed input or failures reported by system facilities, which prevent proper testing from proceeding. The framework attempts to recover from errors, so as to provide as useful a result as possible; but, fundamentally, an error means that the test results are inconclusive. .PP A test session can either be driven from data provided by an input file, or it can be driven by the program alone. The latter case is called .I "ad-hoc testing", and is described in .BR tvec-adhoc (3). This manual page describes file-driven testing. .PP When it begins a session for file-directed testing, the program provides a table of .IR "test definitions" . A test definition has a .IR name , and also specifies a .IR "test function" , a .IR "test environment" , and a table of .IR "register definitions" . Test environments are explained further below. .PP A .I register is a place which can store a single item of test data; registers are the means by which input test data is provided to a test function, and by which a test function returns its results. A test definition's register definitions establish a collection of .I active registers. Each active register has a .IR name , an .IR index , and a .IR type , which are established by its register definition. The register's name is used to refer to the register in the test data file, and its index is used to refer to it in the test function and test environments. The register's type describes the acceptable values for the register, and how they are to be compared, read from the input file, and dumped in diagnostic output. New register types can be defined fairly easily: see .BR tvec_tyimpl (3) for the details. A register definition may describe an .I input register or an .I output register: input registers provide input data to the test function, while output registers collect output data from the test function. The data file provides values for both input and output registers: the values for the input registers are passed to the test function; the values for the output registers are .I reference values against which the test function's outputs are to be checked. .PP The test function is called with two vectors of registers, one containing input values for the test function to read and also reference values, and another for output values that the test function should write; and a .I context provided by the test environment. The test function's task is to exercise the functionality to be tested, providing it the input data from its input registers, and collecting the output in its output registers. It is the responsibility of the test environment or the framework to compare the output register values against reference provided in the input data. .PP The input file syntax is described in full below. In overview, it is a .BR .ini -style file. Comments begin with a semicolon character .RB ` ; ', and extend to the end of the line. It is divided into .I sections by headings in square brackets: .IP .BI [ test ] .PP Each section contains a number of .I paragraphs separated by blank lines. Each paragraph consists of one or more .I assignments of the form .IP .IB reg " = " value .PP or .IP .IB reg ": " value .PP When the framework encounters a section heading, it finishes any test group currently in progress, and searches for a test definition whose name matches the .I test name in the section heading. If it finds a match, it begins a new test group with the same name. Each paragraph of assignments is used to provide input and reference values for a single test. The .I reg name in an assignment must match the name of an active register or a .I "special variable" ; the corresponding .I value is stored in the named register or variable. .PP A register which has been assigned a value is said to be .IR live ; otherwise, it is .IR dead . By default, every active register must be live for a test to proceed; but a register definition can mark its register as .I optional or .IR may-be-unset . An optional register need not be assigned a value explicitly; instead, the register is left dead. A may-be-unset register must be mentioned, but a distinctive syntax .IP .IB reg " *" .PP (with no colon or equals sign) says that the register should be left dead. Optional registers are suitable for cases where there is an obvious default value which would otherwise be mentioned frequently in input files. May-be-unset registers are mostly useful as outputs, where the output is not always set, e.g., in error cases, but where omitting a value in the usual case is likely a mistake. .PP A test environment fits in between the framework and the test function. It can establish hook functions which are called at various points throughout a test group (at the start and and, and before and after each test). It can define special variables which can be set from the input file using assignments. And, finally, it can take on the responsibility of running the test function. The registers will have been set up already, based on the assignments in the input file, but the environment can modify them. It must also check the test function's output against the reference values, though there are functions provided for doing this. The environment can choose to run the test function once, multiple times, or, indeed, not at all. When it calls the test function, it can provide a context pointer, with whatever additional information might be useful: this usually involves coordination between the environment and the test function. It is the test environment's responsibility to check the outputs returned by the test function and to report on mismatches, but there are functions provided by the framework to do the heavy lifting. .PP The following are examples of what test environments can do. .hP \*o It can fill in default values for optional dead registers. For example, if a function returnns error codes, then you can save reptition in the input file by marking the error-code output register optional and letting it default to the `success' value in a test environment. .hP \*o It can run the test function mulitple times. For example, a test of functions like .BR strcmp (3) might run the test twice, first with its operands as supplied by the input file, and then again with the operands swapped and the opposite expected result. A test for bignum addition could verify commutativity by checking both that .IR x "\ + \ " y "\ =\ " z and that .IR y "\ + \ " x "\ =\ " z \fR. Similarly, a subtraction test could check both that .IR x "\ \- \ " y "\ =\ " z and that .IR y "\ \- \ " x "\ =\ \-" z \fR. .hP \*o The .BR tvec-remote (3), .BR tvec-timeout (3), and .BR tvec-bench (3) extensions all slot in as test environments, with no extensions to the core API. .PP The framework includes a simple command-line front-end described in .BR tvec-main (3). In particular, the .B tvec_main function provides all of the necessary command-line option and argument processing, and handles the test session lifecycle, given a pointer to the static tables describing the supported test groups. . .SS Input file format The file format is inspired by Windows .B .ini files. .PP Comments begin with a semicolon .RB ` ; ' and continue to the end of the line; a comment may be placed on a line by itself, optionally preceded by whitespace; or following other contents on a line. .PP Indentation and blank lines are significant; trailing whitespace on a line is generally ignored. Blank lines serve to separate groups of assignments into `paragraphs' as explained below. A line containing only a comment, possibly preceded by whitespace, is ignored, and does not separate paragraphs. An indented line is a .IR "continuation line" , used to continue values over multiple lines. .PP A line beginning with an opening square bracket .RB ` [ ' is a .IR "section heading" , and has the form .IP .BI [ name ] .PP optionally followed by a comment. The .I name may consist of any non-whitespace characters other than the closing square bracket .RB ` ] '. Whitespace is permitted (though not recommended) between the brackets and the .IR name . The .I name must match the name of a test group defined by the test configuration; see below. .PP A section heading marks the start of a .IR section , which continues until the next section heading or to the end of the file. .PP Each section consists of .I assignments divided into .I paragraphs by groups of one or more blank lines. A line containing only a comment is .I not considered to be `blank' for the purposes of separating paragraphs. There need not be blank lines before the first paragraph in a section; nor after the last. .PP A paragraph consists of one or more .IR assignments , each of which begins with an unindented .I initial line followed by zero or more indented .IR "continuation lines" . .PP An initial line begins with a .I name followed by an .IR "assignment operator" ; the remaining contents, if any, are determined by the name and operator. A name is a sequence of non-whitespace characters other than assignment operators, which are colon .RB ` : ', equals sign .RB ` = ', or asterisk .RB ` * '. There may be whitespace separating the name and operator. .PP A .I "value assignment" has the colon .RB ` : ' or equals sign .RB ` = ' as its operator; there is no difference in meaning between the two. The operator is followed by optional whitespace and a .IR value . The .I name must match the name of a register or special variable defined by the test group, and the register or special variable definition determines a register type for the assignment; the syntax of the value is determined by the register type; see .BR tvec-types (3) for the descriptions of the built-in types, and .BR tvec-tyimpl (3) for how to define new register types. The .I value may continue over multiple continuation lines, each beginning with one or more whitespace characters. The effect is to set the named register or special variable to the specified value. .PP An .I unassignment has the asterisk .RB `*' as its operator. The .I name must match the name of a regsiter defined by the test group. Only registers defined with the .B TVRF_UNSET or .B TVRF_OPT flags (see below) can be unassigned; special variables cannot be unassigned. .PP An error is reported if the same register or special variable is the subject of more than one assignment within a paragraph, or if there is no assignment for a register defined by the test group without the .B TVRF_OPT flag. . .SS Miscellaneous values The .B "union tvec_misc" is a small union used to pass a simple but opaque parameter to an extension. It consists of an untyped pointer .BR p , signed and unsigned integers .B i and .BR u , and a floating-point number .BR f . The pointer member is listed first, because this is the most common (and general) case, and the first member of a union is the only member that can be initialized statically in dialects (e.g., C89, C++14) which lack designated initializers. .PP There is an associated collection of constants, .BR TVMISC_PTR , .BR TVMISC_INT , .BR TVMISC_UINT , and .BR TVMISC_FLT , which can be used to keep track of which member of a .B union tvec_misc is `active'. These constants are currently not used by the framework. . .SS Registers A .I register is essentially a `slot' capable of holding a value. Registers primarily occur as inputs and outputs for test functions, but they also appear in other contexts. .PP The values that a particular register can hold are determined by the register's .IR type . Register values are held in a .BR "union tvec_regval" . Unmodified, this union contains a number of useful C-level data types corresponding to the provided register types; see the synopsis above, and .BR tvec-types (3) for the specifics. More members can added by defining the macro .B TVEC_REGSLOTS to the list of member declarations before including .BR . .PP A register value can hold resources, such as allocated memory, or even network connections or files. A register value must be .I initialized before use, and should be .I released when it is no longer required. The life-cycle for register values is described in detail in .BR tvec-tyimpl (3). .PP The state of a register is maintained in .BR "struct tvec_reg" , which contains a flags word .B f as well as a .B "union tvec_regval" to hold the value. .PP At the start of the test session, the framework allocates two vectors of .B "struct tvec_reg" , conventionally named .I in and .IR out , with the lengths .B nreg and .B nrout specified in the test configuration. The .I in vector must be at least as long as the .I out vector, i.e., .BR nreg "\ \*(>=\ " nrout . Within the framework, registers are identified by their indices in these vectors. An index .BI \fR0 "\ \*(<=\ " i "\ <\ " nrout identifies an .IR "output value" ; an index .BI nrout "\ \*(<=\ " i "\ <\ " nreg identifies an .IR "input value"; other indices are invalid. .PP A .I "register definition" associates a name, type, and some flags, with a register index. A register definitions is represented as a .B "struct tvec_regdef" structure, which has five members. .TP .B "const char *name" The register name. This is used in input files to refer to the register. .TP .B "const struct tvec_regty *ty" The register's type. This can be the address one of the supplied .B tvty_...\& type structures described in .BR tvec-types (3), or a custom type; see .BR tvec-tyimpl (3) for how to implement new types. .TP .B "unsigned i" The index of the register. .TP .B "unsigned f" Flags defining additional special behaviour for the register. The flags are described below. .TP .B "union tvec_misc arg" An argument for the register type. .PP A test definition (see below) includes a vector of register definitions. The vector is terminated by an entry whose .B name member is a null pointer. The .B TVEC_ENDREGS macro expands to a suitable static initializer. .PP The names and indices of register definitions for a single test definition must be dinstinct. A test definition need not include a register definition for every register index; the registers with associated definitions in a particular test are said to be .IR active ; those without a definition are said to be .IR inactive . Nothing useful can be done with inactive register indices: inactive register don't even have an associated type. .PP The flags which can be set are as follows. .TP .B TVRF_UNSET It is permitted to .I unassign the register in input file paragraph using the .RB ` * ' assignment operator. .TP .B TVRF_OPT It is permitted for an input file paragraph to have no assignment for the register. It is also permitted for an input file paragraph to unassign the register explicitly, i.e., .B TVRF_OPT additionally implies the behaviour of .BR TVRF_UNSET . .TP .B TVRF_ID The register value constitutes part of the `identity' of the test case. For example, the .BR tvec-bench (3) uses the values of the registers whose .B TVRF_ID flag is set to label its performence results. .PP The .I in vector receives data from the input file. When the framework processes an assignment, it looks up the .I name in the register definitions: if it finds a match, it uses the associated index to locate the register. In the case of a value assignment, the framework invokes the register type's .B parse function to read the value from the input file (see .BR tvec-tyimpl (3) for details on this) and store it in the register. If this is successful, then the framework sets the .B TVRF_SEEN and .B TVRF_LIVE flags on the register. In the case of an unassignment, the framework sets only the .B TVRF_SEEN flag. Registers with indicess equal or greater than .B nrout provide input data to the test function. Registers with indices less than .B nrout hold reference values: a test function won't usually inspect these, though it might, e.g., to determine an output buffer size. .PP Once the end of the paragraph is found, the framework checks that all of the necessary register have been assigned: every register listed in the test group's register definitions must have the .B TVRF_SEEN flag set, unless the definition sets the .B TVRF_OPT flag. It invokes the test function, via the test environment, if one is defined. See below for details on how the test function is invoked. . .SS Test definitions A .I "test definition" describes a test group. Test definitions are represented as a .B "struct tvec_test" structure. This has four members. .TP .B "const char *name" The test group name. This is used in input files to refer to the test group. .TP .B "const struct tvec_regdef *regs" Pointer to the vector of register definitions. The vector is terminated by an entry whose .B name member is null. .TP .B "const struct tvec_env *env" Pointer to the test environment for this test group. Test environments are described in .BR tvec-env (3). Specific uses are discussed in .BR tvec-bench (3), .BR tvec-remote (3), and .BR tvec-timeout (3). .TP .B "tvec_testfn *fn" The test function. . .SS Test functions The signature for test functions is .IP .nf .ta \w'\fBvoid \,\fItestfn\/\fB('u .BI "void " testfn "(const struct tvec_reg *" in , .BI " struct tvec_rec_reg *" out ", void *" ctx ); .fi .PP The .I in and .I out arguments are the two register vectors discussed above, the former holding the input and reference values, and the latter for the outputs. The .I ctx pointer is provided by the test environment. If there is no test environment, then this is null. .PP On entry to the test function, all of the active registers are initialized, both .I in and .IR out ; the .I out registers corresponding to live reference registers in the .I in vector additionally have their .B TVRF_LIVE flags set. .PP Recall that an input-file paragraph can provide a value assignment for a register, an unassignment, or none at all. A test function or test environment can distinguish the three cases using the register flags: .hP \*o A register which has been assigned a value has both .B TVRF_SEEN and .B TVRF_LIVE flags. .hP \*o A register which has been explicitly unassigned has only the .B TVRF_SEEN flag. Such a register's definition must have had either .B TVRF_UNSET or .B TVRF_OPT set. .hP \*o A register which has not been assigned has neither flag set. Such a register's definition must have had the .B TVRF_OPT flag set. .PP The test function will usually set the .I out registers corresponding to at least the live reference registers; it may set additional active output registers (and set their .B TVRF_LIVE flags). .PP The caller will check the outputs against the reference values from the input file. A .I mismatch occurs when a reference-value register is live and either the corresponding output register is not live or the output register value is not equal to the reference value (as determined by the register type). In the event of a mismatch, the framework dumps the values of of the registers and reports a failure. .PP The test function is .I not passed a pointer to the .B "struct tvec_state" structure which is pretty much ubiquitous in the test-vector framework interface. A test function is not expected to `fail' in the sense that it encounters a problem that prevents it from performing the test. Failure-prone setup can be performed by the test environment, which can force a skip if necessary; successfully acquired resources can be passed on to the test function via the context pointer. Errors encountered by the test function itself should usually be reported by setting an output register. If a test function really needs access to the test state, an environment can arrange to pass it in via the context pointer. . .SS Test configurations A .IR "test configuration" , represented by the .B "struct tvec_config" structure, describes the global information required for a test session. It has four members. .TP .B "const struct tvec_test *const *tests" A pointer to a vector of pointers to test definitions, terminated by a null pointer. (Having pointers to the test definitions here, rather than the definitions themselves, allows the actual definitions to be kept alongside the rest of the machinery required for the test group.) .TP .B "unsigned nrout" The number of output registers. .TP .B "unsigned nreg" The number of registers total. (There are therefore .BR nreg "\ \-\ " nrout input registers.) .TP .B "size_t regsz" The size of a .BR "struct tvec_reg" . The fact that a test program can add additional members to the .B "union tvec_regval" by setting .B TVEC_REGSLOTS means that the true size of the union was unknown when the framework and application-agnostic extensions were compiled. They therefore need to learn the true size at runtime. Since the main motivation is to be able to index the .I in and .I out register vectors, it's the size of .BR "struct tvec_reg" , rather than .B "union tvec_regval" itself, that's of critical importance. . .SS Output drivers The framework core does no output itself. Instead, all output is performed by an .IR "output driver" , represented with the .B "struct tvec_output" structure. See .BR tvec-output (3) for details about this structure and how to write new output drivers. This section describes the output drivers provided with the framework. .PP The .B tvec_dfltoutput function is given a .B stdio stream .I fp and returns an output driver chosen in some unspecified way. Currently, if .I fp refers to an `interactive device' then the `human' driver is chosen; otherwise, the `machine' driver is chosen. This behaviour may change in the future. If your program depends on a specific driver being chosen, then use it explicitly. .PP The predefined drivers are as follows. .IP The `human' driver .RS The `human' driver produces output expected to be useful to humans, but, despite being .I technically unambiguous (I think) is probably rather difficult to parse. Moreover, the details of the output are subject to change. .PP The `human' driver prints a line when a test or test group is skipped; where a reason for the skip is given, this is also printed. A line is also printed for each error encountered, describing the situtation; errors are .I also written to the standard error stream if (heurstically) . .SS Test session lifecycle