Developing Drivers with the Windows Driver Foundation (Pro Developer)
Many annotations are straightforward and obvious to write, but some can take a little effort to get right. PREfast attempts to diagnose any errors it sees in annotations, but it cannot check and report every conceivable error. It is always a good idea to write a small test case to confirm that annotations behave as you expect.
A good test case should report any expected errors and should not report instances of correct usage. Simple annotations such as __in do not benefit from test cases, but annotations that involve sizes-in particular, annotations that use the __part modifier-often benefit from test cases because writing the test cases often forces you to think about corner cases.
Examples of Annotation Test Cases
The example in Listing 23-34 shows a set of very simple test cases for the myfun function, which takes three parameters: mode, p1, and p2. The value of mode determines valid values for p1 and p2, so the test cases use the __drv_when conditional annotation to specify how to enforce correct usage of myfun.
Listing 23-34: Example of annotation test cases for a function
// Annotate the prototype. __drv_when(mode==1, drv_arg(p1, __null)) __drv_when(mode==2, drv_arg(p2, __null)) __drv_when(mode <= 0 || mode > 2, __drv_reportError("bad mode value")) void myfun(__in int mode, __in struct s *p1, __in struct s *p2);
Starting with the first annotation, the prototype establishes the following usage requirements:
-
If mode is 1, p1 must be NULL.
-
If mode is 2, p2 must be NULL.
-
If mode is negative or greater than 2, issue a "bad mode value" error message.
In Listing 23-35, the test cases call the function correctly and incorrectly. Incorrect calls cause PREfast to issue a warning.
Listing 23-35: Example of code that exercises annotation test cases
// A correct use void dummy1(__in struct s *a, __in struct s *b) { myfun(0, a, b); } // An incorrect use: expect a warning void dummy2(__in struct s *a, __in struct s *b) { myfun(1, a, b); } // An incorrect use: expect a warning void dummy3(__in struct s *a, __in struct s *b) { myfun(2, a, b); } // A correct use void dummy4(__in struct s *a, __in struct s *b) { myfun(1, NULL, b); } // A correct use void dummy5(__in struct s *a, __in struct s *b) { myfun(2, a, NULL); } // An incorrect use: expect a warning void dummy6(__in struct s *a, __in struct s *b) { myfun(14, a, b); }
Tips for Writing Annotation Test Cases
Consider these tips when writing annotation test cases:
-
It is usually not necessary for the test case code to actually do anything. Often a single function call inside a dummy function is sufficient.
-
If you need pointers to structures, it is often enough to simply have the dummy function take those pointers as parameters, with the appropriate __in, __out, or other annotations that are required for the test.
-
It is not necessary to link or run annotation test cases. It is only necessary to use PREfast to compile them.
-
If you are writing several test cases, it is usually a good idea to put each test case in a separate dummy function. PREfast tries to minimize noise by suppressing duplicate errors within a function. Often when you are checking for multiple ways to trigger the error, the duplicate suppression logic suppresses the additional instances.
-
Functions that return void are usually better for test cases because they are not required to meet the compiler's requirement that the function return a value.
-
Remember to give each function a distinct name.
-
Remember that annotations are independent. It is usually unnecessary to check for unexpected interactions of unrelated annotations.
Категории