SPNBOX Test Files

General Information and Coding Conventions

The SPNBOX functions come with a set of test files used during debugging. The files are contained all contained in the /spnbox/tests/ directory. There are source files to produce one executable for each SPNBOX function. The executables take input from the console or from a file, specified as a single command-line argument.

The input should be formatted to be interpreted by a special set of IO routines, discussed later. Input should describe a single problem, that is, all the parameters for a call to the function the executable tests. When a particular keyword is given, each program displays a summary of the data it was given, makes a test call to the function it tests with the given parameters, and displays the result.

The test programs typically use a single header file, test.h, that includes all the header files that are typically used by a test program. This file also defines a few functions, implemented in test.c, that are useful in many of the test programs.

The first such function is FILE* ParseCmdLine(int argc, char* argv[]), which takes as parameters the same parameters used by an executable to retrieve its command-line arguments and returns a pointer to the stream which should be used for input. ParseCmdLine examines the arguments to the program. If there are any arguments the first is interpreted as a filename. ParseCmdLine attempts to open the file for input and return a pointer to it. If the open fails ParseCmdLine returns a null pointer. If there are no arguments, ParseCmdLine displays a message informing the user they may type "help" for information about commands and returns a pointer to stdin.

Another such is FillDmDp. This function is useful in the context of the I/O routines because they make parameters optional and return an integer flag array to indicate which ones have been filled in. In order to allow users to enter either complete incidence matrices (more convenient since it contains the data in Dm and Dp in one matrix) or separate input and output matrices if the Petri net contains self-loops, the programs typically allow the user all three options and let them enter what they choose. FillDmDp examines three flags in an integer array, assuming that the flags represent the filled state of D, Dm, and Dp, and if D has been given will use the pns functions to fill Dm and Dp with the appropriate matrices.

Finally, test.h defines is_verbose() to return the value of a global integer from test.c. This integer can be set with a call to void setverbose(int), defined in test.h & c.

The general structure of a typical test program is to use ParseCmdLine() to retrieve a pointer to the input file. Then, an description of what variables are needed for that particular function type is placed in a string (character array). This description is in a format used internally by the specialized IO routines. Miscellaneous problem variables are defined here as well. The core of the program is a loop that continues until the function ParseStructure(), which uses the text description to read in variables for a single function call, returns 0, indicating that a keyword was found in the input stream requesting program termination. In the body of the loop, the variables filled by the ParseStructure() are displayed (written to stdout) and then formatted as necessary to prepare them for use in a call to the function being tested. The function call is made and the results are displayed. Memory allocated by the ParseStructure() call and in the function return value is freed. Then the loop returns to its beginning and ParseStructure is called again. At the end of the program any residual memory is freed.

Note that there are a few routines for which the test program is different, and a few for which there is no test program because they are used subroutines of other thoroughly tested programs.

In addition, test scripts have been provided for the test programs. These contain text in the correct format to test all the functionality of a given function. The naming convention is test-functionname.txt.

Building Test Routines

The makefile in the tests folder can make each of the executables to test each of the programs. Each executable is a single target, and each one has the name of the function being tested. Thus, to test ipsolve, one would use the following sequence of commands (beginning from the pntool root directory).

cd spnbox/tests
make ipsolve
./ipsolve test-ipsolve.txt

Note that the makefile can, as an option, link in a third-party memory debugging library located in the /third-party/memwatch-2.71/ directory. This library wraps the memory allocation and deallocation functions and keeps track of allocations and frees. It also attempts to catch wild pointer writes and other memory-related errors. See the later section on memwatch for more information. The inclusion of this library in the test routines slows them down by orders of magnitude, so its inclusion is optional, set by the setting of the "USEMEMWATCH" variable which is defined in the first few lines of the test makefile. When this variable is set to "yes", the memwatch libraries will be included. When it is set to any other value they will be left out.

High Level I/O Routines (StructuredIO.c)

StructuredIO.h and .c define a pair of routines intended to read and display complex data such as matrices and vectors in a human-readable format. There are two functions.

int ParseStructure(FILE *file, char* DataDescription, int** FilledMask, MemoryManager* Memory, ...) is for reading in data. The first parameter is a file pointer to the stream from which input is to be taken. This can be stdin. DataDescription is a null-terminated string containing a description of the variables that define a single problem. This includes names, the keywords that will be used to indicate that a particular variable follows when they are encountered in the input stream, and the type of variable. For more information about the format of this string, see the comments in StructuredIO.h. The next parameter, FilledMask, is a pointer to an integer pointer that will be filled with an array allocated by ParseStructure(). There will be one element for each variable that is to possibly be filled, set to 0 if that variable was not found in the input stream before the end-of-problem keyword was encountered, and set to nonzero if the keyword was found and the variable was filled. The MemoryManager pointer points to a MemoryManager structure (which should have been previously initialized) which is used to record all the allocations made by the call to ParseStructure(). This is followed by optional parameters, pointers to variables corresponding to those defined in the DataDescription. The function reads the input stream looking for keywords, after which are assumed to come the values of the variables associated with those keywords. The data is read and put into the variables pointed to by the optional parameters. There are also several predefined keywords which cause ParseStructure() to perform other tasks. When the "echo" keyword is encountered the remainder of the line is read and printed to stdout. When "rem" is encountered the rest of the line is ignored. When "help" is encountered a message describing basic usage and whatever keywords have been set up by the DataDescription is printed - "help 'keyword'" returns help on a specific predefined keyword. "done" ends the current problem, causing ParseStructure() to return a nonzero value. "quit" is interpreted as a request to terminate the program, causeing ParseStructure to return zero. For more information about keywords and data formats see the StructuredIO.h header file.

The second function, void DisplayStructure(char* DataDescription, int* Mask, ...) is used to display the same kinds of data as that read by ParseStructure in a friendly format. Similar parameters have similar meanings. For more information on how the data is displayed, see the StructuredIO.h header file.

Third-Party Member Debugging Library (memwatch.c)

The memwatch library is a third-party library intended to help fing memory-related bugs such as wild frees, wild pointer writes, overwrites, and underwrites. It functions by wrapping C's memory functions with its own, which log allocations and deallocations, allocate buffers around allocated pieces of memory to detect overwrites and underwrites, and implements several other algorithms to help detect memory errors. Each time a program the libraries have been included in executes, memwatch creates (or appends to if it already exists) a log file, memwatch.log. This log file will include notes of any errors or anomolies the memwatch routines detect. Because the routines slow down any memory-related operations by orders of magnitude, their inclusion in the test routines is optional and can be enabled or disabled as described in the "Building Test Routines" section.

More information on the memwatch routines can be found in several files, /third-party/memwatch-2.71/README, /third-party/memwatch-2.71/USING, and in the primary header file, /third-party/memwatch-2.71/memwatch.h.