Device Driver Test Tool (DDTT)

From EDM2
Jump to: navigation, search
Storage Device Driver Reference
  1. About This Book
  2. Introduction to DASD, SCSI, and CD-ROM Programming Interfaces
  3. Installation of OS/2, DASD, SCSI, and CD-ROM Device Drivers
  4. Adapter Device Driver Development Considerations
  5. DASD, SCSI, and CD-ROM Device Manager Interface Specification
  6. Error Handling
  7. Adapter Device Driver Command-Line Parameters
  8. DASD IOCtl Device Driver Test Tool
  9. Optical IOCtl Device Driver Test Tool
  10. Using Filter Device Drivers
  11. Library and Services
  12. CD-ROM Device Manager Interface Specification
  13. CD-ROM Device Driver Test Tool
  14. Building an OS/2 Virtual Disk Driver
  15. OS2DASD.DMD - Technical Reference
  16. Boot Record Architecture
  17. Extended Device Driver Interface Specification
  18. I/O Request Block - C Definitions
  19. OS/2 SCSI Device Driver Interface Specification
  20. Advanced SCSI Programming Interface (ASPI) OS/2 Specification
  21. Adapter Device Driver Interface Questions and Answers
  22. Device Driver Test Tool (DDTT)
  23. Glossary

Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation

The Device Driver Test Tool (DDTT) provides an efficient environment to create, execute, and refine device driver test cases. The DDTT is extensible by the addition of new device-dependent DLLs and grammar files. DLL files implement device-specific interface functions, such as DosDevIOCtl calls. Actual test-case content and execution is controlled by test-case script files. Test-case script files are parsed by DDTT's generic parser. Device- specific functions resident in the DLLs are indirectly called from the test-case parser. Device-specific grammar files tell the parser which device-specific function and parameter keywords to expect in the test-case script files.

Using the DDTT

Accelerated development of test interfaces to new devices is achieved with the DDTT by isolating the device-specific calls in "stub routines", which are compiled and linked into a separate DLL file. Linkage to the device- interface routines is established at run-time by demand loading the DLL functions. The DLL name and function name information is obtained from the device-specific grammar file. The DDTT parser reads a test-case file and locates a @NEWALIAS command. The @NEWALIAS command contains the name of a DDTT grammar file for all commands prefaced with the specified new alias name. The parser uses the device grammar file name to map all device-specific commands to the correct DLL and function name within the DLL.

Overall Data Flow Diagram

The following data flow diagram shows the major components of the DDTT. Shaded modules represent components that are intended to be modified or re-created when adding new support for devices and test cases.

Note: When adding new C++ device interface modules, it is necessary to provide a device-dependent grammar file that provides run-time linkage information to DDTT's parser.


Development Environments

The DDTT provides the following three development environments:

  • Test Case Execution
  • Test Case Development
  • Ring-3 Device Interface
Test-Case Execution

Test case executors are the intended users of the DDTT at this level and is a procedural interface. DDTT test cases are contained in flat text files called Script Files. Typically, each script file contains one test case, although a test case could span multiple files by utilizing the DDTT's script file's include capability.

To start a test case contained within a file named TEST01.SCR, type DDTT TEST01.SCR from an OS/2 command prompt.

Operator prompts are displayed in separate windows, one window for each test thread. Any resulting logs are written to the file names specified on the @THREAD commands in the test script file.

At the Test-Case Execution level, the DDTT requires limited knowledge of OS/2 (for example, how to start and stop command files, and any device-specific knowledge required by the test-case grammar file.

DDTT scripted test cases are repeatable. When a test case is defined and contained within a Test-Case Script File, the execution and subsequent re-executions of the test case always execute functions within a thread in the same order. Although intra-thread operations occur in the same order, there is no guarantee that inter-thread operations will always occur in the same order. Use the DDTT's shared file logging to determine test-case execution order in multiple thread test cases.

Test-Case Development

When a device's function set has been defined, additional test cases can be created by adding or modifying existing test-case scripts. Thus, adding new test cases for an existing device requires knowledge of that device and an flat file editor. Using DDTT's scripting capabilities, new test cases can be quickly developed to cover any "holes" identified in existing test "buckets".

In addition to device-specific functions, DDTT test case developers can use all of the functions described in #Script Utility Functions.

The interface provided for the test case script writer is procedural. Commands are executed top-to-bottom within a given test-case thread.

Built-in generic functions are provided for creating test case scripts. Generic functions are independent of any device under test. Syntax of these functions is the same across all DDTT device interfaces.

Ring-3 Device Interface

The C++ DLL modules implement DDTT device-specific stub functions. Creating DDTT device interfaces requires knowledge of the C++ programming language and a detailed understanding of the device driver to be tested (such as which functions the device is capable of performing). The DDTT provides many common functions, such as log file management and cover routines for the OS/2 APIs.

DDTT device stub function developers can use all of the functions described in #Stub Utility Functions.

Script Utility Functions

Test-case script functions are device-specific or generic. Generic or utility-script functions are part of all DDTT grammar files. Most DDTT commands are associated with an aliasname. The general form of a DDTT command is as follows:

 alias_name FUNCTION_NAME [[PARAMETER=value], ...]

The DDTT built-in functions which are not associated with an aliasname are preceded with an "at"(@) character.

Script Utility Function keywords and their parameters are case insensitive and all tokens are uppercased by the DDTT. In the descriptions below, upper case represents fixed tokens or keywords, and lower case indicates a variable token, specific to the particular function invokation. To preserve a token's case, use the syntax $(Token).

All DDTT commands are automatically continued to the next line if the last character in the line is the backslash character (\).

The DDTT test case files can use OS/2 environment variables. Environment variables can be used anywhere in the script. Any valid symbolic token with a (%) sign preceding it will be interpreted as an OS/2 system environment variable. To establish or change an environment variable from an OS/2 prompt, type the following:

[C:\]set cdrom=cdrom.scr.

The environment variable can then be referenced from within a test case script as follows:

@thread %cdrom.

(%cdrom will then be replaced by cdrom.scr.)

@Thread log_filename

This function starts the new thread of execution. Any resulting DDTT file logs will be written to log_filename. The DDTT shares access to log_filename, and the same log_filename may be used for multiple @THREAD commands within the same test case. The DDTT also creates a separate OS/2 Presentation Manager window for every @THREAD token. Any operator messages and prompts generated in the @THREAD section are displayed in the corresponding window.

Each occurrence of the @THREAD token marks the start of a section in the test case script file which is to be executed sequentially under the control of a single OS/2 thread created by DDTT. DDTT parses the entire test case file looking for @THREAD tokens. When the entire test case file (including any @IMPORT commands) has been parsed, DDTT creates and starts an OS/2 thread for each @THREAD token located. All threads are initially created within the DDTT process with the following characteristics:

Stack size:   4KB


  • Thread windows are created on top of each other. To access multiple windows simultaneously, drag the thread windows to separate places on the screen
  • All OS/2 threads are started at approximately the same time.
  • The DDTT must encounter an @THREAD command as the first command after resolving all @IMPORT commands in the test case file.
  • The DDTT test scripts can start no more than 50 threads.
@NEWALIAS alias_name grammar_file

Create a DDTT alias name and establish the device-specific grammar to use when parsing all other commands with the same alias_name in the test case. The first required parameter is the alias_name and can be any alpha-numeric string. The specified alias_name is the first token in all commands that follow and share the same family of parameter keywords.

The second required parameter specifies the grammar file to be used when parsing all commands which start with the alias_name token. The DDTT's parser uses the content resolve all device-grammar file function, the parameter keyword and the expected value keywords used in the test case commands.

@IMPORT import_filename

Insert the content of the specified import_filename into the test-case file. DDTT test-script files can include or insert other DDTT test script files. Up to ten levels of imported files are allowed. An environment variable may be used to contain the file name of the file to be included.

@LOOP loop_cnt | [alias_name, numeric_parameter_kwd]

Execute the following block of one or more scripted functions up to the next @ENDLOOP token in a loop. Nested loops are permitted. The number of loop iterations is controlled in one of two methods. A single loop count-positive numeric value may be specified or a DDTT environment variable may be used.

The other method is syntactically different, but allows specification of a DDTT parameter_keyword as the loop count. Place the DDTT alias_name and a numeric parameter_keyword defined by the alias inside square brackets {[]}. Use a comma separator between these tokens.


  • The value used for the loop count is the current value of the parameter_ keyword at the start of the loop. The actual value of the parameter_ keyword is not changed by executing the @LOOP command.
  • The current loop count value is not accessible from within the loop.
@LOG "string"

This function logs the only parameter to the log file opened with the previous @THREAD command. The log entry is time-stamped. If the log command string contains script variables, the script variables will be expanded before the text is placed in the log file. Every log file entry made from the script interface will be prefaced with the current time/date stamp from the real-time clock of the machine under test. The quotation marks are required only if the string contains white space.

@PAUSE seconds

This function stops execution of all aliases in the current thread for the specified number of seconds. Execution is automatically resumed. No tester action is required.

@FLUSH alias_name

This function releases buffers and closes the log file for the current thread.

alias_name SET parameter_name=value

This function initializes or reinitializes the current value of the DDTT keyword parameter_name to the string value. Any previous value is erased. Multiple keyword parameters may be set in one DDTT SET command. Separate each parameter equate with white space. All keyword parameters are associated with the specified alias_name.

A keyword parameter_name need not exist or be expected; no warning or error is generated if a SET command is performed on a nonexistent parameter_name. The parameter_name will exist after executing the SET command.

Type checking of values for parameter_names is not performed with the SET command, instead, type checking is done when DDTT invokes the device function.

alias_name LOG $PROMPT="String to be logged"

This function generates a time-stamped entry in the log file opened at the previous @THREAD command. The string specified in the $PROMPT keyword is also written to the DDTT thread window during test case execution. If the $PROMPT keyword is not set, then a time-stamp is written to the log file.

alias_name RESPONSE $PROMPT="String to be output" $PAUSE=seconds $RESPONSE=param_kwd

This function displays a tester prompt string contained in the $PROMPT parameter keyword and returns any tester input in the parameter keyword referenced by $RESPONSE. The $PAUSE keyword can be used to specify the number of seconds to wait for the tester to start inputting the response. As long as the tester has pressed at least one keystroke before $PAUSE seconds has elapsed, DDTT will wait with an infinite timeout for the remainder of the input. Input is terminated by the enter key.

If no value is specified for the $PROMPT keyword, this function causes test case execution to pause for $PAUSE seconds. If $PAUSE is not defined or specified as 0, then no tester input will be accepted, but the $PROMPT is displayed. If $PAUSE is specified to be -1, the DDTT will wait for a tester response with an infinite timeout.

Note: Place the cursor in the input window before providing tester input to a DDTT thread panel. The cursor does not automatically go to the thread input window.

alias_name INCREMENT

This function increments the DDTT $COUNT keyword parameter in alias_name by 1.

alias_name DECREMENT

This function decrements the DDTT $COUNT keyword parameter in alias_name by 1.

alias_name ADD

This function adds the DDTT $OP1 and $OP2 keyword parameters and places the result into the $RESULT keyword parameter.

alias_name SUB

This function subtracts the DDTT $OP2 keyword parameter from $OP1 and places the result into the $RESULT keyword parameter.

alias_name COMPARE BUFFER1=alpha BUFFER2=beta

This function compares the two DDTT keywords. It returns BUFFER COMPARE SUCCESS or BUFFER COMPARE FAILURE.

alias_name DUMP $BUFFER=alpha

This function will place the contents of a buffer in the log file.

Named Data Buffers

The DDTT test scripts can access named buffers by way of device-dependent read and write operations. The named buffer is device-independent and may be used with different devices from within the same test script. A named data buffer is initialized by referencing the named buffer as part of a device-read operation. Data from a device can be read into a named data buffer and then written to another device or file. Comparison of two named data buffers and dumping a buffer to a log file is provided. Named buffer comparison and dumping operations are provided as device-independent functions.

OS/2 API Cover Functions

A set of DDTT OS/2 API-cover routines is provided. These routines detect and report error conditions returned by OS/2 API calls. Formatted error messages do not have to be created for each API called; however, if the API returns an error, it will be reported in the log file. The provided cover APIs have the same name as their OS/2 counterparts but are prefaced with ddt. Likewise, the calling parameters are also identical, except for three additional parameters added after the standard parameters:

IString pszCallingRoutineName;  
// Name of the device specific stub function
// calling routine. This IString is printed 
// as part of any error message generated in
// the API cover routine.

IString pszLocationDescription;
// Any text the DLL writer wants included 
// with the error message text from the API 
// cover routine.

OutputStream &output; 
// OutputStream to send any error or 
// informational messages from the API cover 
// routine.

Listed below is the current set of OS/2 APIs for which DDTT has implemented cover routines: ddtDosOpen ddtDosRead ddtDosWrite ddtDosDevIOCtl ddtDosClose ddtDosPhysicalDisk The writer of a device-specific DLL must include ddtos2.h.

#include ddtos2.h

Below is a sample prototype of one of the most frequently used API cover routines.

   APIRET _export ddtDosDevIOCtl(
                  HFILE hDevice,
                  ULONG category,
                  ULONG function,
                  PVOID pParams,
                  ULONG cbParmLenMax,
                  PULONG pcbParmLen,
                  PVOID pData,
                  ULONG cbDataLenMax,
                  PULONG pcbDataLen,
                  IString pszCallingRoutineName,
                  IString pszLocationDescription,
                  OutputStream &output);

Stub Utility Functions

The functions and classes described in this section are intended for use in developing interface stub functions and are accessed through a C++ interface. Although these functions are written in and accessed through C++, these functions can be called from device stub functions that are primarily written in C. The utility classes described are the following:

IString Kwd_List DdtOStream Buffer
IString Class

The IString class is the most basic of the utility classes. Items of type IString contain and manipulate character data. Data types other than characters can be stored in a IString object, however, other types of data will first be converted to a character representation. IString objects automatically grow and shrink as their content is manipulated and are not pre-allocated to a known size.

The complete information on the functionality of the IString class is found in the ISTRING.HPP header file. This class and associated files were taken from what became part of a user interface class library in the IBM C++ product. The following is a subset of the constructor methods available to create IString objects:

IString( const char* );
IString( int );
IString( const IString& );
IString( char );
IString( const void *pBuffer1, unsigned lenBuffer1, char padCharacter=' ' );

The following is a subset of the methods available for IString objects:

long asInt() const;
unsigned size() const;
unsigned length() const;
IString subString( unsigned startPos, unsigned length=0, char padCharacter=' ' ) const;

The following is a subset of the overloaded operator methods available for IString objects:

char &operator[]( unsigned index );
IString &operator=( const IString &aString );
IString operator + ( const IString &aString ) const;
IString operator + ( const char *pString ) const;
friend _export IString operator +
   ( const char *pString, const IString &aString );
IString operator - ( const IString &aString ) const;
IString operator - ( const char *pString ) const;
friend _export IString operator -
   ( const char *pString, const IString &aString );
IString &operator += ( const IString &aString );
IString &operator += ( const char *pString );
operator char*() const;
const char &operator[]( unsigned index )

The == operator prototyped below works similarly for operators: !=, <, <=, >, and >=.

  friend _export Boolean operator == ( const IString &string1,
       const IString &string2 );
  friend _export Boolean operator == ( const IString &string1,
       const char *pString2 );
  friend _export Boolean operator == ( const char *pString1,
       const IString &string2 );

IString objects can be automatically instantiated on the stack as shown below. This example creates a IString object named str:

  #include "istring.hpp"
  IString str;

IString objects can also be dynamically instantiated by way of a constructor method. Below is an example of constructing a IString and referencing it with a pointer named pstr:

  #include "istring.hpp"
  IString * pstr;
  pstr = new IString("ABC");

IStrings act as a repository for data given to them. All data given to an IString object is first converted to ASCII character data and then copied into the IString. Thus, after constructing an IString, data may be deposited into it such as the following:

str = "Output from YourFunction:\n";

The creating and initialization of a IString object may be combined as the following:

IString str("Output from YourFunction:\n");

Used as above in the first example, with the = operator, any existing content of the IString item is entirely replaced with the new data, Output from YourFunction.

The IString class provides additional overridden operators for manipulating the content of an IString object. To concatenate data to an existing IString item, use the +=operator:

str += (IString)"Input ABC returned XZY\n";


str = (IString)"Input ABC " + (IString)"returned XYZ\n";

The following is also an eraser or subtraction operator which deletes the first occurrence of the specified substring.

(str = str - (IString)"returned")

If the above three IString operations were performed in sequence, str would contain the following:

Output from YourFunction:\n
  Input ABC  XZY\n

Notice the use of the (IString) casting operator on the RHS operands when using the + operator. This function is necessary for the + and - overridden IString operators in order to keep the compiler from getting confused and trying to do the wrong type of "addition" or "subtraction".

In addition to character strings, IString items can also accept other data types. IString items recognize these other data types and convert the data into a character string. The user must tell the IString what type of data is being input through the use of casting operators, for example:

str += (int)101;

The integer will be converted to its base 10, character equivalent and the resultant content of str would be the:

 Output from YourFunction:\n
 Input ABC  XZY\n

IString items can accept for input and convert the following data types:

 (const char *)   // Deref's ptr, copies in string, %s
 (char)           // Converts to ASCII, %c
 (IString &)      // Deref's ref, copies in IString
 (int)            // Converts integer to ASCII, %d
 (void *)         // Converts pointer to integer ASCII, %p

One last overridden operator is the square brackets, [], which provide similar function as they do for normal C strings except the index origin begins with 1. They provide a convenient mechanism for indexed access to individual characters within the IString. The index has an origin of 1. From the above example, str[2] is currently equal to the character u.

IString substrings can be accessed through the following subString method:

  str = (IString)"ABCEFG";
  IString str2 = str.subString(3,3);

After executing these lines, str2 contains CEF. Although IString objects store all data internally as character data, several methods are provided to retrieve data types other than characters. For example:

   int i;
   IString str;
   i = 5;
   str = (IString)2 + (IString)i;
   i = str.asInt();  // Returns IString as an integer

After this code executes, str[1] = '2', str[2] = '5', and i = 25.

A length method is included that returns the current number of characters stored in the following IString object:

i = str.length();

results in:

i = 2.
Kwd_List Class

A reference to a Kwd_List object is passed into every stub function. The Kwd_List object contains all the parameters for the current alias name. Through operations on the Kwd_List object, values of parameters may be set or retrieved from the current alias the test script. By referencing the same Kwd_List parameter, two or more stub functions can share a parameter. Each Kwd_List is a List object. Each item "on the" Kwd_List is also an object and consists of a Keyword and a Value. Both Keyword and Value are IString objects. Think of the Keyword as that parameter's "ASCII handle" and Value as the current content or value of that parameter. The DDTT creates an entry for each parameter keyword specified in the associated grammar file for the current DDTT alias. Thus, the stub function's view of the Kwd_List object is primarily that of accessing and updating the existing entries in the Kwd_List.

The following constructor method is available to create Kwd_List objects:

  Kwd_List();            // Creates an empty list

The following methods are available for Kwd_List objects:

   // Add or update a keyword-value pair in the  Kwd_List
   void set(IString kwd, IString value);

   // Remove a keyword-value pair from the Kwd_List
   void unset(IString kwd);

   // Return an sscanf'd int corresponding to a keyword
   long int getInt(IString kwd);

   // Return an sscanf'd pointer corresponding to a keyword
   void * getPtr(IString kwd);

   // Return true if the keyword is known
   BOOL isKeyword(IString kwd);

   // Return the number of entries in the Kwd_List
   int nKey();

   // Return a pointer corresponding to the FILES keyword
   FileInfo * files();

The following overloaded operator methods are available for Kwd_List objects:

   // Return the corresponding value to a keyword
   IString operator[](IString kwd);

   // Numerically indexes the list, return  keyword-value
   void operator()(int inx, IString &kwd, IString &value);

Below is a declaration of a sample stub function:

   #include "kwdlist.h"
   APIRET SampleStubFtn( Kwd_List &param )
   { // code here };

The only parameter to all stub functions must be a Kwd_List object reference. Assume the grammar file specifies the following parameter keywords:


Also assume that input kwd_list reference is named param, as in the above example stub declaration.

To access DEVICENAME as an IString use the overloaded operator: []

  IString name;
  name = param["DEVICENAME"];

The overloaded operator [] expects an IString object as input, (an input variable of type const char * works also, because const char * can convert themselves to IString objects). The [] operator then automatically converts to IString, performs the lookup in the Kwd_List, param, and returns a copy of the value associated with the parameter keyword. A stub function can change the current Value of an item on the input Kwd_List by way of the set method.

param.set("READTRYS", (long)6);

This function sets the current value of the READTRYS parameter keyword to 6. This procedure will overwrite any value set in the test case script file to this point in the script file's execution.

Keyword parameter values can be retrieved as long integers and pointers respectively, by way of the getInt getPtr methods:

  long loop_cnt;
  char * huge_buf;
  loop_cnt = param.getInt("READTRYS");
  huge_buf = param.getPtr("OUTPUTBUFFER");

The unset method has a more permanent effect on the input parameter's keyword param.unset("READTRYS");. This unset operation removes READTRYS from Kwd_List. After executing this method, any future reference to the READTRYS parameter keyword within the execution of the current DDTT alias would return an empty IString object. The unset method drops the entire Keyword and Value pair from the input Kwd_List. As such, the unset method should be used with caution! The isKeyword method tests the Kwd_List object to determine if the specified parameter keyword is currently in the Kwd_List object.

  int test;
  // This will return TRUE
  test = param.isKeyword("READTRYS");
  // This will return FALSE.
  test = param.isKeyword("RETRYCNT");

Returns TRUE if the keyword is on the list, FALSE otherwise.

  int kwd_count;
  kwd_count = param.nKey();

Returns the number of keywords currently in the Kwd_List object. By convention, keywords that begin with a "$" character are built-in, automatic variables. Existing #define macros in the DDTKWDS.H file ensure proper error checking by the compiler. For example:

  // Pointer to associated FileInfo object
  #define FILES_KEY   "$FILES"
  // Pointer to associated BufferList object
  // Current DO, FOR loop index value
  #define COUNTER     "$I"

Access to the protected log file output stream is obtained through the $ FILES parameter keyword. The required C++ syntax is relatively complicated ; therefore, a special Kwd_List method, files(), is provided to simplify access to the standard logging output stream:

  IString s1;
DdtOStream Class

The DdtOStream Class is a subclass of OutputStream. DdtOStream objects provide a mechanism for sending one or more lines of output data to a log file used simultaneously by multiple threads. The output data sent to an OutputStream or DdtOStream is kept contiguous within the log file. Colliding requests wait for the current request to complete before their output is sent to the file. Although it is unlikely a stub writer would create new DdtOStream objects, the following constructor method is available to create DdtOStream objects:

  DdtOStream(IString &filename);

The following method is available for DdtOStream objects:

  // Set or reset the automatic thread identifier prefix
  void settid(IString),

The following overloaded operator methods are available for Kwd_List objects:

  // Send the IString object to the DdtOStream
  void operator<<(IString &str);
  // Send the Buffer object to the DdtOStream
  void operator<<(Buffer &buf);

The bit-shift operator is overloaded to "look like" the redirection operator and is used to send IStrings to the output log:

  #include "threadio.h"
  param.files()->out1<<(IString)"Hello world!";

The only difference between a DdtOStream and an OutputStream is that each output to a DdtOStream is precluded with the current Thread Identification. The current threadID is set for DdtOStream when DDTT executes an @THREAD command. The current ThreadID can be changed through the settid method: out1.settid("New thread name");.

Buffer Class

The Buffer class provides a memory buffer object that can be used to contain blocks of data read and written to devices. Buffer objects must always be created by the stub writer. If they are attached to the Kwd_List by way of the BUFFER_LIST keyword, then Buffer object destruction will occur automatically by DDTT.

When a test script writer references a buffer name in a test script such as in the DEV_ALIAS READ_SECTOR BUFFER1=ALPHA, ALPHA is only the specified name of the buffer, not the actual Buffer object. The function stub must create the actual Buffer object and associate the name, ALPHA, with the created Buffer object. The function stub uses the DDTT keyword, BUFFER1, to access the Buffer's specified name, ALPHA.

The following constructor method is available for Buffer objects:

  Buffer(const char * name, size_t size, void * = NULL);

The following methods are available for operating on Buffer objects:

  // Returns a pointer to the Buffer's data
  PVOID buffer();
  void reallocate(size_t size);
  int compare(Buffer&, size_t);
  PCHAR kwd();
  size_t size();

The following overridden operator methods are available for operating on Buffer objects:

  // Replace RHS Buffer with a copy of LHS Buffer
  Buffer& operator=(Buffer& buf2);
  // Compare Buffer objects
  BOOL operator==(Buffer&);

The #include "buffer.h" include statement must be at the top your C++ stub module in order to use Buffer objects. The following are some examples showing usage of the C++ methods provided to operate on Buffer objects.

  size_t size = 2048;
  Buffer * buf;
  buf = new Buffer( param["BUF_NAME"], size,

The above statement constructs a new Buffer object with memory block of length size bytes associated with the test script keyword BUF_NAME. The constructor also adds the new buffer to a BufferList attached to the Kwd_ List, by the keyword BUFFER_LIST. By specifying the last parameter in the constructor, a BufferList pointer, DDTT, will automatically destroy the Buffer object when the current thread terminates. The BufferList parameter can be left out or specified as NULL, in which case, the new Buffer object is not attached to the Kwd_List.

However, the user is then responsible for deleting the Buffer object when it is no longer required by the stub functions. The delete buf; function destroys the Buffer object and deallocates its memory. For example:

  Buffer buf;
  void * ptr;
  ptr = buf.buffer();

The delete buf; function returns a pointer to the buffer's internal block of memory. For example:

  void * datap;
  datap = (param.getPtr(param["BUFFER"]))->buffer();

Buffer objects can be easily "dumped" to the standard DDTT log file as:

  Buffer buf1;

The content of Buffer object buf1 is formatted in hexadecimal ASCII and is sent to the standard log file. This file returns a pointer to the Buffer object's memory block. The Buffer object is referenced by the keyword parameter BUFFER in the test script.

  size = 4096;

This function re-sizes the buffer to "size." The data in the old block is lost.

  Buffer buf, buf2;
  buf2 = buf;

The = operator is overridden to copy the block from one Buffer to another, re-sizing the destination Buffer if necessary.

  if( buf == buf2 )...

The == operator is overridden to return TRUE if two Buffers are identical; otherwise, the == operator is overridden to return FALSE.

  if(, (size_t)1024) );

This function compares buf to buf2 for the specified number of bytes, or fewer bytes if one buffer is smaller than the specified number. It also returns the value from memcmp(). (0 if the buffers are equal, and so on.)

  size_t size2;
  size2 = buf.size();

This function returns the size of the buffer in bytes.

  Buffer * bufp;
  bufp = new Buffer("TEMP_BUF", (size_t)10000, NULL);

This function deletes Buffer pointed to by bufp.

Adding New Grammar Specifications

A grammar file is a flat text file that specifies the stub functions available for a particular DDTT test grammar. Within DDTT, the scope of a grammar is the alias. Each alias specified in a test script associates a single grammar with the alias. For example:


The above line from a test script file creates the ABC alias and associates the grammar specified in the file, ABC_GRAM.GRA , with the ABC alias. All further references to functions called within the ABC alias will use the ABC_GRAM.GRA grammar file to resolve the DDTT stub function entry point.

Every DDTT callable stub function has a corresponding entry in a grammar file. The grammar file provides the mapping between the function name as it is referenced in the test script file and the actual entry point in a DDTT stub function DLL file. See Sample Grammar Specification for a sample DDTT grammar file.

Specifying Comments

The DDTT grammar files should serve as the primary mechanism for communicating function and parameter details to the test script writer. As such, use of comments is encouraged. All lines, with an asterisk (*) as the first character, specify a comment line in the DDTT grammar file and its entire content is ignored by the parser.

* this is a comment
Stub Function Entry Specification

Each DDTT callable stub function is defined by a Stub Function Entry Specification. The following is syntax for a grammar file Stub Function Entry Specification:

func_kwd $DLL=dll_name                     \
         $FUNC=@func_name$qr8Kwd_List      \
  [param_kwd=param_type]...                \
  [$EXPECTED [param_kwd=param_type]...]

The complier name of the stub function's DLL entry point is @func_name$ qr8Kwd_List. The name shown here is specific to Borland's OS/2 Version 1.5 C++ compiler. The function, func_name, is as it appears in the C++ function stub source. Use the prefix (@) and the appropriate compiler mangling suffix for all stub functions called by the DDTT. Where param_type is defined as one of the following parameter type tokens:

param_type=NUM|ALPHA|ALNUM|STRING Param_type token definitions:

       NUM    - All characters in this parameter must be numeric,
                [0..9, +, -, .]
       ALPHA  - All characters in this parameter must be alphabetic,
                [a..z, A..Z]
       ALNUM  - All characters in this parameter must be alphabetic or 
                numeric,[a..z, A..Z, 0..9, +, -, .]
       STRING - Any character string or nothing (null string) may be 
                specified.  This is the default param_type.

Each complete Stub Function Specification must be on a single logical line. Actual lines in the grammar file may be extended by placing backward slash, (\) as the last non-blank character in the line. For OS/2, the dll_name must be a valid DDTT DLL file with the location specified in LIBPATH. The name of the stub function, func_kwd, is used in the test case script file. All func_kwds must be unique within a grammar file. The same param_kwd may be used for different functions. If the same param_kwd is used, then only one parameter keyword is created and the value of this parameter keyword is used for all functions specifying the same param_kwd. One or more param_kwds with param_type may be specified to pass data into the DDTT stub function. Param_kwds defined before the $EXPECTED keyword in the Stub Function Entry Specification are required. At runtime, DDTT's parser will not call a function specified in the test script unless all required param_kwds are defined and of the correct type. If the user attempts to call a stub function with incorrectly specified parameters, DDTT prints an error message identifying which function is not being called and what parameter(s) are incorrect. If a script file calls a stub function without specifying EV_ or ET_ values for all $EXPECTED parameters then DDTT calls the function, checks any specified parameters and also prints a warning message to the log file specifying which expected parameters are missing.

Automatic Expected Value Checking

If the Stub Function includes a $EXPECTED keyword then the list of param_ kwd(s) which follow will be automatically verified by DDTT. Use of the $ EXPECTED param_kwd list permits the user to specify expected results in the test case script file. The DDTT will automatically compare the specified expected results with the param_kwd's actual resultant value. If the results match, then no log message is produced. If the value or type fails to compare then an ERROR message is logged which show the actual and expected results. Each param_kwd specified in the grammar file's $EXPECTED list may contain a param_type specifier. It is not necessary to specify a param_type if the param_kwd is a required input parameter and as such has already been specified in the Stub Function Specification. If the parameter is not a required input then the param_type specifier is required . The test case writer may verify the expected results two ways:

  • Prefix: ET_ - Verify result has the same param_type, NUM, ALNUM, ALPHA, or STRING as the associated param_kwd.
  • Prefix: EV_ - Verify the result matches the expected value of the associated param_kwd.

The test case script writer specifies which verification method by placing either an "EV_" or "ET_" prefix on the param_kwd to be verified. Consider the following example grammar and test script files:

  * Example grammar entry, video.gra
  @import global.gra
  read_video  $DLL=vidotest               \
        $FUNC=@MMVID_GETCLIP$qr8Kwd_List  \
        MODE=ALNUM                        \
        START_FRAME=NUM                   \
        FRAME_COUNT=NUM                   \
        $EXPECTED                         \
           NEXT_FRAME=NUM                 \
           ACT_FRAME_CNT=NUM              \
  * Example Test Case Script
  @newalias alias_1 video.gra
  alias_1 read_video                      \
        MODE=UNCOMPRESSED                 \
        START_FRAME=100                   \
        FRAME_COUNT=30                    \
        EV_NEXT_FRAME=131                 \
        EV_ACT_FRAME_CNT=30               \

The above example grammar defines the read_video function. Before this function can be invoked, the test script must have previously defined the input parameters: MODE, START_FRAME, and FRAME_COUNT. The script writer can also provide definitions for expected values, "EV_", or specify that DDTT verify the param_types with the "ET_" prefix for the returned parameters: NEXT_FRAME, ACT_FRAME_CNT, and FRAME_TYPE. If the script writer does not define expected values with the "EV_" or "ET_" prefix for all of the param_kwds listed under $EXPECTED, then DDTT will by default issue a warning that no parameter checking is taking place for the missing param_kwd(s). After executing the above example, DDTT verifies NEXT_FRAME has the value of 131, ACT_FRAME_CNT has the value of 30, and FRAME_TYPE returns an alpha- numeric string. DDTT logs an error message if any of these conditions are not met. The script writer may turn off all parameter verification by setting the param_kwd $EXPECTED to "off". Parameter verification may be re-activated by setting it back to "ON".

  alias_1 set $EXPECTED=OFF
  *   Parameter Verification is now disabled.
  alias_1 set $EXPECTED=ON
  *   Parameter Verification is now re-enabled.

API returns may be automatically checked for expected non-zero returns by capturing the APIRET from the DDTT API cover routines and setting the value into a DDTT keyword variable such as: APIRET_01. Extend the associated grammar specification for the function under test as:

     $EXPECTED                   \

Test cases which call a function instrumented as such will be required to specify an expected value for APIRET_00 and if it fails to match, DDTT will automatically send an error message to the log file.


Some examples of various pieces of code are included to further illustrate the Device Driver Test Tool:

  • Grammar file for a set of DDTT stub functions.
  • Script file that includes most commands supported by the parser.
  • Function stub source file.

Sample Grammar Specification

   * Sample grammar file for a set of DDTT stub
   * functions.

   * Empty lines are ignored.

   * The @IMPORT function imports the contents
   * of the specified filename into this file.
   * Global.gra is needed to define generic
   * DDTT test case script functions such as:
   * set, log, and loop

   * CD_OPEN -
   * Open a channel to the CDROM device.
   * Required Input Parameters:
   *    DEVICENAME   - Name of the CDROM drive
   *                   to open, i.e., E:.
   * Output Parameters:
   *    DRIVEHANDLE  - Open Drive handle to
   *                   CDROM device.
   CD_OPEN  $DLL=DDTCDROM                     \
            $FUNC=_cdrom_devopen              \
   * CD_CLOSE -
   * Close channel to the CDROM device.
   * Required Input Parameters:
   *    DRIVEHANDLE  - Open Drive handle to
   *                   CDROM device.
   * Output Parameters:
   *    -none-
   CD_CLOSE $DLL=DDTCDROM                     \
            $FUNC=_cdrom_devclose             \
   * CD_EJECT -
   * Eject disk from the CDROM drive.
   * Required Input Parameters:
   *    DRIVEHANDLE  - Open Drive handle to
   *                   CDROM device.
   * Output Parameters:
   *    -none-
   CD_EJECT $DLL=DDTCDROM                     \
            $FUNC=_cdrom_eject                \
   * Play a selected audio track from the audio
   * CDROM.  The starting location may be
   * specified in either Logical Block format
   * or Redbook (time offset) format.
   * Required Input Parameters:
   *    DRIVEHANDLE  - Open Drive handle to
   *                   CDROM device.
   *    ADDRESSMODE  - Addressing mode, one of:
   *                   LOGICAL_BLOCK
   *                   REDBOOK -
   *                    minutes/seconds/frames
   *    START_SECTOR - First block to play
   *    END_SECTOR   - Last block to play
   CD_PLAYAUDIO                               \
      $DLL=DDTCDROM                           \
      $FUNC=_cdrom_playaudio                  \
      DRIVEHANDLE=NUM                         \
      ADDRESSMODE=STRING                      \
      START_SECTOR=ALNUM                      \

Sample Test Script

   * This is an example script file. It
   * includes most commands supported by the
   * parser.
   * @thread takes a filename as a parameter
   * to open as * the thread log file
   @thread test
   * @log accepts a string to be logged to the
   * log file specified in the immediately
   * preceding @thread command.  All entries to
   * the log file are preceded with the current
   * date/time stamp taken from the system
   * real-time clock.
   @log "Beginning of thread 1"
   * @newalias takes a unique alias name and
   * name of device grammar file to be opened
   * for input
   @newalias mycd cdrom.scr
   * @import takes a filename as a parameter.
   * The filename is a script filename which
   * contains script commands that follow
   * conventions described in this file.
   @import filename
   * example device directive lines.  The first
   * token is a previously opened @newalias
   * name, 2nd token is device specific
   * function keyword as declared in the device
   * grammar file.
   mycd cd_close
   mycd cd_open
   * The following commands/functions are
   * available to any grammar that imports
   * global.gra
   * For all global functions the first
   * token is a previously-opened alias name,
   * 2nd token is a generic function name.
   mycd set param_kwd=value
   mycd message "some string to be prompted"
   mycd log "Send this text to the log file."

Sample Function Stub Source

   * Name: APIRET _export sample_stub(Kwd_List param)
   * Description: Sample stub function.
   * Parameters:  Kwd_List &.
   * Returns:     APIRET.
   * Cautions:
   APIRET _export sample_stub(Kwd_List &param)
     APIRET apiret;
       BYTE one;
       BYTE two;
       BYTE three;
     } paramblock;

     ULONG ulParamsize = sizeof(paramblock); = param.getInt("ONE");
     paramblock.two = param["TWO"];
     paramblock.three = param.getPtr("THREE");
       BYTE  data1;
       BYTE  data2;
     } data;

     ULONG  ulDataSize = sizeof(data);
     HFILE  hfDrvHandle =

     apiret = ddtDosDevIOCtl(hfDrvHandle, 0x99,
              &paramblock, ulParamsize,
              &data, ulDataSize, &ulDataSize,
              "Sample Stub",
              "only call",
     // Let DDTT check the APIRET
     param.set("APIRET_00", (IString)apiret );
     if (apiret == 0)
       IString s1;
       s1 = "The result is:";
       s1 += "\n\tdata1 :";
       s1 += (LONG)data.data1;
       s1 += "\n\tdata2 :";
       s1 += (LONG)data.data2;
     return 0;