Using Test Doubles: Difference between revisions

From STRIDE Wiki
Jump to navigation Jump to search
 
(43 intermediate revisions by 4 users not shown)
Line 1: Line 1:
==Introduction==
__NOTOC__
The ''Function Double'' feature provides a means for intercepting C language functions on the target, and directing the call to a substitute function with identical parameters and return value. The use case is a unit test where the function under test uses a ("C") function during its execution, and this dependency is simulated by a substitute or double function during testing. The unit test is able to control the substitution of the dependency during run time, and thereby verify the behavior of the function under test.
The ''Test Double'' feature provides a means for intercepting C/C++ language '''global functions''' on the target, and directing the call to a substitute function with identical parameters and return value. The use case is a unit test where the function under test uses a function during its execution, and this dependency is simulated by a substitute or double function during testing. The unit test is able to control the substitution of the dependency during run time, and thereby verify the behavior of the function under test.
The following sample illustrates the relationship of the function under test and a dependency:
The following sample illustrates the relationship of the function under test and a dependency:


Line 8: Line 8:
</source>
</source>
   
   
<source lang="c">
<source lang="c">
// depend.c
// depend.c
Line 17: Line 18:
}
}
</source>
</source>
   
   
<source lang="c">
<source lang="c">
Line 23: Line 25:
</source>
</source>
   
   
<source lang="c">
<source lang="c">
// test.c
// test.c
Line 34: Line 37:
</source>
</source>


In the above sample, test() is the function under test and depend() is a dependency candidate for doubling.


In the above sample, ''test()'' is the function under test and ''depend()'' is a dependency candidate for doubling.


The steps required to achieve doubling of a dependency function are as follows:
The steps required to achieve doubling of a dependency function are as follows:
#[[#Configuring_the_Double_Using_SCL|Configure the double parameters using SCL pragmas]]  
#[[#Configuring the Double Using Function Pragma | Configuring the Double Using Function Pragma]]  
#[[#Creating_Double_Intercepts_in_the_IM|Create the double intercepts in the IM]]
#[[#Creating_Double_Intercepts_in_the_IM|Create Double Intercepts in the IM]]
#[[#Switching_the_Double_Function_During_Runtime|Switch to/from the double function during runtime]]
#[[#Switching_the_Double_Function_During_Runtime|Switch Double Function During Runtime]]


==Configuring the Double Using SCL==


The syntax of the [[Scl_func|scl_func]] and [[Scl_function|scl_function]] pragmas have been expanded to include new '''''optional''''' attributes that allow the specification of function interception parameters. If these options are omitted, then the function is not a candidate for doubling.
==Configuring the Double Using Function Pragma==


#pragma scl_function(function-name [,<intercept>])
The syntax of the [[Scl_function|function pragma]] supports a set of '''''optional''''' attributes that allow the specification of function interception parameters. When captured for the purpose of interception ('''intercept-able''') the optional arguments are '''required'''.
  #pragma scl_func(SUID, function-name [,<intercept>])
 
  #pragma scl_function(function-name [,context, name-mangling, group-id])
<intercept>    = <context>, <name_mangling>, <group_id>
<context>      = “REFERENCE” | “DEFINITION” (case insensitive)
<name_mangling> = “EXPLICIT” | “IMPLICIT” (case insensitive)
<group_id>      = [user defined identifier] (i.e. “^[A-Za-z_]\w*$”)


===Context Option===
The <context> option in the syntax guide above allows either the '''"REFERENCE"''' or '''"DEFINITION"''' strings. A "REFERENCE" context means the intercept is made at the function call, i.e., the intercept function is called instead of the dependency. A "DEFINITION" context means that the intercept is made at the function definition, i.e., the intercept function name is equivalent to the dependency name, and and is called in place of the dependency. These options are equivalent to the [[Intercept_Module#Owner.2FUser|user and owner delegate options]] that are in place for generating [[Intercept_Module#Delegate|delegates]].


===Mangling Option===
Refer to the actual [[Scl_function | function pragma]] definition for an explanation of
The <name_mangling> option above allows either the '''"EXPLICIT"''' or '''"IMPLICIT"''' strings. These settings are used to solve [[Name_Mangling#Name_Mangling_Conflicts|name mangling conflicts]] within your target source. "EXPLICIT" mangling requires the use of the "'''imINTERCEPT('''&lt;function_name&gt;''')'''" macro, which explicitly mangles the name of "f()" to "__im_f()". This macro is defined in the generated Intercept Module delegate mangling header file (xxxIM.h). Explicit mangling requires [[Intercept Module#Group ID|Group ID(s)]] to be defined (#define MyGroupID) in the source file of the caller/callee of the routine to protect against the inclusion of unnecessary defined types. "IMPLICIT" mangling performs no mangling, and requires none of these steps.
* '''context''' - set to ''DEFINITION'' or ''REFERENCE''
* '''name-mangling''' - set to ''EXPLICIT'' or ''IMPLICIT''. Note if set to explicit requires usage of the ''srTEST_INTERCEPT'' macro
* '''group-id''' - user defined to enable or disable interception


===Group ID Option===
As described in the above mangling option section, a [[Intercept Module#Group ID|Group ID]] is required for EXPLICIT mangling. The <group_id> string is specified in the pragma as shown. The Group ID(s) must be defined your source before including the Intercept Module delegate mangling header file (xxxIM.h). Please refer to an example of when and how this is done [[Name_Mangling#Mangling_Conflict_Example_2:_Two_users_of_a_routine.2C_f.28.29.2C_exist_in_the_same_source_file.2C_but_only_one_is_a_user_delegate|here]].


===Dependency Interception===
In the above sample, ''depend()'' needs to be captured via the pragma and enabled for interception in the following manner.
In the above sample, depend() needs to be SCL captured in matter to be doubled.


<source lang="c">
<source lang="c">
Line 72: Line 67:
#pragma scl_function(depend, "DEFINITION", "IMPLICIT", "TEST_GROUP")
#pragma scl_function(depend, "DEFINITION", "IMPLICIT", "TEST_GROUP")
</source>
</source>


==Creating Double Intercepts in the IM==
==Creating Double Intercepts in the IM==
If a function has been configured as a double candidate using SCL as outlined in the above step, then the next step is to compile and bind using [[Build_Tools|STRIDE build tools]] and create the IM that contains the intercept for the double function. The options to [[S2sinstrument|s2sinstrument]] have been updated to support the function double feature as follows:
If a function has been configured as a double candidate using the function pragma as outlined in the above step, the [[Build_Tools|Stride Build Tools]] will automatically create the [[Intercept Module]], aka IM, that contains the intercept for the double function. This all happens automatically during the build process.


{| border="1" cellspacing="0" cellpadding="10" style="align:left;" 
But to enable the interception the source file containing the <tt>depend()</tt> implementation '''must be altered''' to [[Name_Mangling|mangle the function's name]] by defining the Group ID and including the generated Intercept Module header file (xxxIM.h):
| '''Option'''
| '''Change'''
| '''Notes'''
|-
| '''--default_mode'''=<mode>[#<group_id>]
| Deprecated
| Use the new format below.
|-
| '''--default_mode'''=<mode>
| New
| Optional. When present, sets the default generation mode. If not present the <mode> is assumed to be “I” for intercept-able functions and “S” for all others. <br>
The format of the <mode> is "SPI[T]", where:<br>
“S” – stub<br>
“P” – proxy<br>
“I” – intercept<br>
“T” – trace
|-
| '''--mode'''=<mode>[#<group_id>] (<interface>[,<interface>…])
| Deprecated
| Use the new format below.
|-
| '''--mode'''=<mode>(<interface>[,<interface>…])
| New
| Interface specific generation mode. The <mode> is the same as above. The <interface> is an interface name.<br>
This option could be repeated as many times as needed. It overrides any --default_mode option otherwise in effect.
|}


Where the allowed mode combinations are:
<source lang="c">
{| border="1" cellspacing="0" cellpadding="10" style="align:left;"
// depend.c
| '''S'''
#include "depend.h"
| '''P'''
/* define the Group ID before including the IM header */
| '''I'''
#define TEST_GROUP
| '''T'''
#include "strideIM.h"
| '''Notes'''
|-
| X
|
|
|
| Generate Stub
|-
|
| X
|
|
| Generate Proxy
|-
|
|
| X
|
| Generate Intercept for a Function Double (see '''''Note''''' below)
|-
|
| X
| X
|
| Generate Intercept for Dynamic Delegate
|-
|
|
| X
| X
| Generate Intercept for Trace Delegate
|-
|
| X
| X
| X
| Generate Intercept for a Dynamic Delegate with Tracing
|}


'''''Note:''''' Currently the only option that supports Function Doubles is the "I" option ''by itself''. Any other option combinations will not allow the double substitution at run-time as outlined in the next step. Also note that the absence of any options will default to the "I" option alone per interface.
int depend(int x)
{
    return x + x;
}
</source>


==Switching the Double Function During Runtime==
==Switching the Double Function During Runtime==
The test unit will have access to the following STRIDE runtime macros for substituting a stub function for a double candidate.
In the context of the test unit code the following STRIDE runtime macros, defined in the STRIDE runtime header file '''srtest.h''', could be used for substituting a stub function for a double candidate.


<source lang="c">
<source lang="c">
srDOUBLE_GET(fn, pfnDbl)
srDOUBLE_SET(fn, fnDbl)
srDOUBLE_SET(fn, fnDbl)
srDOUBLE_RESET(fn)
srDOUBLE_GET(fn, pfnDbl) // used for more advanced scenarios
</source>
</source>


Where:
Where:
*'''fn''' is the function qualified by scl_function or scl_func as a dependency candidate, as above.
*'''fn''' is the function qualified by <tt>scl_function</tt> or <tt>scl_func</tt> as a dependency candidate, as above.
*'''pfnFbl''' is a pointer to a object of type srFnDbl_t, declared to hold the current value of the active double function.
*'''pfnDbl''' is a pointer to a object of type <tt>srFunctionDouble_t</tt>, declared to hold the current value of the active double function.
*'''fnDbl''' is a function that is to be the current active double. ''The function passed in should '''always''' match the signature of the dependency candidate specified by '''fn'''.''
*'''fnDbl''' is a function that is to be the current active double. ''The function passed in should '''always''' match the signature of the dependency candidate specified by '''fn'''.''
'''''Note:''''' the initial value of the current active double is always the dependency candidate function.
'''''Note:''''' the initial value of the current active double is always the dependency candidate function.


These macros are defined in the STRIDE runtime header file '''sr.h'''. The following example shows how they are used in a [[Test_Units#C.2B.2B_Test_Classes|C++ test unit]]:
=== Example 1 ===
The following example shows how to double a routine for the lifetime of a C++ Test Unit:


<source lang="cpp">
<source lang="cpp">
#include <srtest.h>
#include <srtest.h>
#include "depend.h"


extern "C" int depend_dbl(int a) { return a * a; }
extern "C" int depend_dbl(int a) { return a * a; }
Line 178: Line 118:
     Test()
     Test()
     {
     {
        srDOUBLE_GET(depend, &m_depend_dbl);
         srDOUBLE_SET(depend, depend_dbl);
         srDOUBLE_SET(depend, depend_dbl);
     }
     }
Line 184: Line 123:
     ~Test()
     ~Test()
     {
     {
         srDOUBLE_SET(depend, m_depend_dbl);
         srDOUBLE_RESET(depend);
     }
     }


     int test1(void) { return test(1, 2); }
     int test1(void) { return test(1, 2); }
     int test2(void) { return test(5, 6); }
     int test2(void) { return test(5, 6); }
private:
  srFnDbl_t m_depend_dbl;
};
};
   
   
#ifdef _SCL
#ifdef _SCL
#pragma scl_test_class(Test)
#pragma scl_test_class(Test)
#pragma scl_function(depend, "DEFINITION", "IMPLICIT", "TEST_GROUP")
#endif  
#endif  
</source>
</source>


[[Category: Reference]]
=== Example 2 ===
The following example shows how to make a call to the original routine in the context of a double by safely handling any nested doubling:
 
<source lang="c">
#include <srtest.h>
#include "depend.h"
 
extern "C" int depend_dbl(int a)
{
    int ret;
 
    srFunctionDouble_t fn;
    srDOUBLE_GET(depend, &fn);
    srDOUBLE_RESET(depend);
    ret = depend(a);
    srDOUBLE_SET(depend, fn);
   
    return ret;
}
</source>

Latest revision as of 20:08, 6 July 2015

The Test Double feature provides a means for intercepting C/C++ language global functions on the target, and directing the call to a substitute function with identical parameters and return value. The use case is a unit test where the function under test uses a function during its execution, and this dependency is simulated by a substitute or double function during testing. The unit test is able to control the substitution of the dependency during run time, and thereby verify the behavior of the function under test. The following sample illustrates the relationship of the function under test and a dependency:

// depend.h
int depend(int x);


// depend.c
#include "depend.h"
 
int depend(int x)
{
    return x + x;
}


// test.h
int test(int x, int y);


// test.c
#include "test.h"
#include "depend.h"
 
int test(int x, int y)
{
    return depend(x) * y;
}


In the above sample, test() is the function under test and depend() is a dependency candidate for doubling.

The steps required to achieve doubling of a dependency function are as follows:

  1. Configuring the Double Using Function Pragma
  2. Create Double Intercepts in the IM
  3. Switch Double Function During Runtime


Configuring the Double Using Function Pragma

The syntax of the function pragma supports a set of optional attributes that allow the specification of function interception parameters. When captured for the purpose of interception (intercept-able) the optional arguments are required.

#pragma scl_function(function-name [,context, name-mangling, group-id])


Refer to the actual function pragma definition for an explanation of

  • context - set to DEFINITION or REFERENCE
  • name-mangling - set to EXPLICIT or IMPLICIT. Note if set to explicit requires usage of the srTEST_INTERCEPT macro
  • group-id - user defined to enable or disable interception


In the above sample, depend() needs to be captured via the pragma and enabled for interception in the following manner.

// depend_scl.h
#include "depend.h"

#pragma scl_function(depend, "DEFINITION", "IMPLICIT", "TEST_GROUP")


Creating Double Intercepts in the IM

If a function has been configured as a double candidate using the function pragma as outlined in the above step, the Stride Build Tools will automatically create the Intercept Module, aka IM, that contains the intercept for the double function. This all happens automatically during the build process.

But to enable the interception the source file containing the depend() implementation must be altered to mangle the function's name by defining the Group ID and including the generated Intercept Module header file (xxxIM.h):

// depend.c
#include "depend.h"
/* define the Group ID before including the IM header */
#define TEST_GROUP
#include "strideIM.h"

int depend(int x)
{
    return x + x;
}

Switching the Double Function During Runtime

In the context of the test unit code the following STRIDE runtime macros, defined in the STRIDE runtime header file srtest.h, could be used for substituting a stub function for a double candidate.

srDOUBLE_SET(fn, fnDbl)
srDOUBLE_RESET(fn)

srDOUBLE_GET(fn, pfnDbl) // used for more advanced scenarios

Where:

  • fn is the function qualified by scl_function or scl_func as a dependency candidate, as above.
  • pfnDbl is a pointer to a object of type srFunctionDouble_t, declared to hold the current value of the active double function.
  • fnDbl is a function that is to be the current active double. The function passed in should always match the signature of the dependency candidate specified by fn.

Note: the initial value of the current active double is always the dependency candidate function.

Example 1

The following example shows how to double a routine for the lifetime of a C++ Test Unit:

#include <srtest.h>
#include "depend.h"

extern "C" int depend_dbl(int a) { return a * a; }

class Test: public stride::srTest
{ 
public:

    Test()
    {
        srDOUBLE_SET(depend, depend_dbl);
    }

    ~Test()
    {
        srDOUBLE_RESET(depend);
    }

    int test1(void) { return test(1, 2); }
    int test2(void) { return test(5, 6); }
};
 
#ifdef _SCL
#pragma scl_test_class(Test)
#pragma scl_function(depend, "DEFINITION", "IMPLICIT", "TEST_GROUP")
#endif

Example 2

The following example shows how to make a call to the original routine in the context of a double by safely handling any nested doubling:

#include <srtest.h>
#include "depend.h"

extern "C" int depend_dbl(int a) 
{
    int ret;

    srFunctionDouble_t fn;
    srDOUBLE_GET(depend, &fn);
    srDOUBLE_RESET(depend);
    ret = depend(a);
    srDOUBLE_SET(depend, fn);
    
    return ret; 
}