|
BDE 4.39.x Production Release
|
Provide a test facility for assertion macros.
Provide a test facility for assertion macros.
This component provides a facility to test that BSLS_ASSERT_* macros are used as intended, in the appropriate build modes, and have the expected effects. The class bsls::AssertTest provides a small set of static methods that can be used to support detailed test cases, especially in table-driven test scenarios. Additionally, a set of macros automate use of these methods to support simple testing of single statements or expressions.
A testing-specific handler guard, bsls::AssertTestHandlerGuard, is also provided to be used wherever the BSLS_ASSERTTEST_* macros are used.
"Negative testing" is the principle of testing for a negative result, which implies the function under test must fail in some way. Testable failures typically occur when a function is called with values outside the defined contract: a well-implemented function will validate function arguments, in appropriate build modes, using the various BSLS_ASSERT macros (see bsls_assert ). When a function fails as a result of an assertion, the default behavior is to terminate the program. However, the bsls_assert facility allows a user-supplied assertion-failure handler function to be installed, which can be used to build a test facility for expected assertions.
One important issue to be aware of with negative testing is that you are testing undefined behavior within a program. For the purpose of the test driver, the behavior of calling a function outside its contract is well- defined if it is guarded by assertions that are active in the current build mode. However, it is important that those tests are not run if the assert macros are not active, otherwise truly undefined behavior will result, with potentially disastrous consequences.
It is important to note that this facility relies on throwing and catching an exception in order to identify that an assertion has been violated, cleanup any objects created on the way to that assertion, and avoid executing any of the code after that assertion with deliberately bad input. This means that this component cannot be used to test assertions in functions that are noexcept, particular destructors that are implicitly noexcept in C++11 and beyond.
For most functions, if you have a narrow contract you should not be noexcept, as this is guaranteeing part of your behavior when your contract is violated (and actively preventing you from doing negative testing in this manner). For functions such as a destructor that are implicitly noexcept and greatly benefit from being so, it is advisable to move the checks into a separate validate method, and test destruction out of contract by just testing that the validate method asserts instead.
The function bsls::AssertTest::failTestDriver (and the parallel function bsls::AssertTest::failTestDriverByReview) is provided as the basis for a negative testing facility. It can act as an assertion-failure handler function that throws an exception, of type bsls::AssertTestException, containing the text of the failed assertion, the name of the file where it triggered, and the relevant line number within that file. The filename can be tested to ensure that the assertion was raised by the component under test, rather than by some deeper implementation detail as a consequence of the expected assertion not being present in the function under test.
Once the function bsls::AssertTest::failTestDriver has been registered as the active assertion-failure handler, a set of testing macros automate much of the boilerplate code involved in writing a negative test, so that a test can be effectively written as a single line. This is an important quality for reading tests, to clearly see the test logic in action without being distracted by the surrounding machinery.
The five basic test macros are
BSLS_ASSERTTEST_ASSERT_PASSBSLS_ASSERTTEST_ASSERT_SAFE_FAILBSLS_ASSERTTEST_ASSERT_FAILBSLS_ASSERTTEST_ASSERT_OPT_FAILBSLS_ASSERTTEST_ASSERT_INVOKE_FAIL Each of these macros takes a single statement or expression as an argument, tests whether an assertion is raised while evaluating that expression, and, if an assertion is both raised and expected, whether that assertion was raised by the component under test.A test failure is indicated by invoking ASSERT(EXPRESSION), where ASSERT is either a macro or function that must be defined by the test driver, and EXPRESSION is an expression that evaluates to true or false according to whether the ASSERTTEST_ASSERT macro was expected to _PASS or _FAIL.
For example, if we have std::vector<int> v and v is empty, then the macro test BSLS_ASSERTTEST_ASSERT_SAFE_FAIL((v.back())) will fail when the effective assertion-level is BSLS_ASSERT_LEVEL_ASSERT_SAFE unless an assertion is raised. However, if the assertion-level is not BSLS_ASSERT_LEVEL_ASSERT_SAFE, then the test will not be run.
The four "raw" test macros are
BSLS_ASSERTTEST_ASSERT_SAFE_FAIL_RAWBSLS_ASSERTTEST_ASSERT_FAIL_RAWBSLS_ASSERTTEST_ASSERT_OPT_FAIL_RAWBSLS_ASSERTTEST_ASSERT_INVOKE_FAIL_RAW These testing macros perform the same test as the corresponding basic testing macros, except that there is no check to confirm that the assertion originated in the component under test.In order to enable the negative testing facility, you must:
#include this component header, bsls_asserttest.h.ASSERT macro in your test driver.bsls::AssertTest::failTestDriver as the active assertion-failure handler (preferably with an instance of AssertTestHandlerGuard).An additional external macro, BSLS_ASSERTTEST_VALIDATE_DISABLED_MACROS, can be defined to control the compile time behavior of bsls_asserttest . Enabling this macro configures all disabled asserttest macros to still instantiate their statements (in blocks that will not be evaluated) to be sure that the statement is still syntactically valid. This can be used to ensure tests that are rarely enabled remain syntactically valid.
Note: this behavior is currently always enabled and cannot be turned off.
Another external macro, BSLS_ASSERTTEST_CHECK_LEVEL, can be used to add an additional check that the assertion that fails is of the same level or narrower than the macro testing the assertion. This will ensure that in all build modes where the assertion is enabled the test for that assertion will also be enabled.
Note that some variations of language contracts might not support the checking of levels when testing assertions, and in those cases the macro BSLS_ASSERTTEST_CAN_CHECK_LEVELS will not be defined.
Seven additional PASS macros exist to parallel the remaining FAIL macros.
BSLS_ASSERTTEST_ASSERT_SAFE_PASSBSLS_ASSERTTEST_ASSERT_SAFE_PASS_RAWBSLS_ASSERTTEST_ASSERT_PASS_RAWBSLS_ASSERTTEST_ASSERT_OPT_PASSBSLS_ASSERTTEST_ASSERT_OPT_PASS_RAWBSLS_ASSERTTEST_ASSERT_INVOKE_PASSBSLS_ASSERTTEST_ASSERT_INVOKE_PASS_RAW These macros are all functionally identical to BSLS_ASSERTTEST_ASSERT_PASS. They exist so that PASS checks format consistently with the corresponding negative tests they are associated with in a test driver. See {Example 2}, below, for how this can help formatting assertion testing code.First we will demonstrate how "negative testing" might be used to verify that the correct assertions are in place on std::vector::operator[]. We start by supplying a primitive vector-like class that offers the minimal set of operations necessary to demonstrate the test case.
Next we implement the support functions.
We conclude the definition of this support type with the implementation of the operator[] overload. Note the use of BSLS_ASSERT_SAFE, which is typical for function template definitions and inline function definitions. It is most appropriate in this case as the cost of evaluating each test is significant (> ~20%) compared to simply returning a reference to the result.
Finally, we can write the function to test that the BSLS_ASSERT_SAFE macros placed in operator[] work as expected. We want to validate that the assertions trigger when the function preconditions are violated; we further want to validate that the assertion macros are enabled in the build modes that we expect. We start by defining some macro aliases that will make the test driver more readable. These macro aliases are a common feature of test drivers.
Then we implement the test function itself. Note that we check that exceptions are available in the current build mode, as the test macros rely on the exception facility in order to return their diagnostic results. If exceptions are not available, there is nothing for a "negative test" to do. We also avoid warnings from UBSAN builds.
If exceptions are not available, then we write a diagnostic message to the console alerting the user that this part of the test has not run, without failing the test.
When testing the various inputs to a function to be sure that some trigger an assertion and some are in contract, it often helps to align the testing macros so that the various arguments are easily readable in relation to one another. We start by defining additional macro aliases to match the existing aliases already defined:
Considering the function testVectorArrayAccess from {Example 1}, we could instead implement it without padded white space by using ASSERT_SAFE_PASS to replace ASSERT_PASS, matching the existing ASSERT_SAFE_FAIL tests, like this:
Typedefs | |
| typedef bsls::AssertTestHandlerGuard | bsls_AssertTestHandlerGuard |
| typedef bsls::AssertTest | bsls_AssertTest |
| #define BSLS_ASSERTTEST_ASSERT_ACTIVE_FLAG false |
| #define BSLS_ASSERTTEST_ASSERT_FAIL | ( | STATEMENT_UNDER_TEST | ) | BSLS_ASSERTTEST_DISABLED_IMP(STATEMENT_UNDER_TEST) |
| #define BSLS_ASSERTTEST_ASSERT_FAIL_RAW | ( | STATEMENT_UNDER_TEST | ) | BSLS_ASSERTTEST_DISABLED_IMP(STATEMENT_UNDER_TEST) |
| #define BSLS_ASSERTTEST_ASSERT_INVOKE_FAIL | ( | STATEMENT_UNDER_TEST | ) | BSLS_ASSERTTEST_DISABLED_IMP(STATEMENT_UNDER_TEST) |
| #define BSLS_ASSERTTEST_ASSERT_INVOKE_FAIL_RAW | ( | STATEMENT_UNDER_TEST | ) | BSLS_ASSERTTEST_DISABLED_IMP(STATEMENT_UNDER_TEST) |
| #define BSLS_ASSERTTEST_ASSERT_INVOKE_PASS | ( | STATEMENT_UNDER_TEST | ) | { BSLS_ASSERTTEST_EVAL_STATEMENT_IMP(STATEMENT_UNDER_TEST) } |
| #define BSLS_ASSERTTEST_ASSERT_INVOKE_PASS_RAW | ( | STATEMENT_UNDER_TEST | ) | { BSLS_ASSERTTEST_EVAL_STATEMENT_IMP(STATEMENT_UNDER_TEST) } |
| #define BSLS_ASSERTTEST_ASSERT_OPT_ACTIVE_FLAG false |
| #define BSLS_ASSERTTEST_ASSERT_OPT_FAIL | ( | STATEMENT_UNDER_TEST | ) | BSLS_ASSERTTEST_DISABLED_IMP(STATEMENT_UNDER_TEST) |
| #define BSLS_ASSERTTEST_ASSERT_OPT_FAIL_RAW | ( | STATEMENT_UNDER_TEST | ) | BSLS_ASSERTTEST_DISABLED_IMP(STATEMENT_UNDER_TEST) |
| #define BSLS_ASSERTTEST_ASSERT_OPT_PASS | ( | STATEMENT_UNDER_TEST | ) | { BSLS_ASSERTTEST_EVAL_STATEMENT_IMP(STATEMENT_UNDER_TEST) } |
| #define BSLS_ASSERTTEST_ASSERT_OPT_PASS_RAW | ( | STATEMENT_UNDER_TEST | ) | { BSLS_ASSERTTEST_EVAL_STATEMENT_IMP(STATEMENT_UNDER_TEST) } |
| #define BSLS_ASSERTTEST_ASSERT_PASS | ( | STATEMENT_UNDER_TEST | ) | { BSLS_ASSERTTEST_EVAL_STATEMENT_IMP(STATEMENT_UNDER_TEST) } |
| #define BSLS_ASSERTTEST_ASSERT_PASS_RAW | ( | STATEMENT_UNDER_TEST | ) | { BSLS_ASSERTTEST_EVAL_STATEMENT_IMP(STATEMENT_UNDER_TEST) } |
| #define BSLS_ASSERTTEST_ASSERT_SAFE_ACTIVE_FLAG false |
| #define BSLS_ASSERTTEST_ASSERT_SAFE_FAIL | ( | STATEMENT_UNDER_TEST | ) | BSLS_ASSERTTEST_DISABLED_IMP(STATEMENT_UNDER_TEST) |
| #define BSLS_ASSERTTEST_ASSERT_SAFE_FAIL_RAW | ( | STATEMENT_UNDER_TEST | ) | BSLS_ASSERTTEST_DISABLED_IMP(STATEMENT_UNDER_TEST) |
| #define BSLS_ASSERTTEST_ASSERT_SAFE_PASS | ( | STATEMENT_UNDER_TEST | ) | { BSLS_ASSERTTEST_EVAL_STATEMENT_IMP(STATEMENT_UNDER_TEST) } |
| #define BSLS_ASSERTTEST_ASSERT_SAFE_PASS_RAW | ( | STATEMENT_UNDER_TEST | ) | { BSLS_ASSERTTEST_EVAL_STATEMENT_IMP(STATEMENT_UNDER_TEST) } |
| #define BSLS_ASSERTTEST_BRUTE_FORCE_IMP | ( | RESULT, | |
| LVL, | |||
| STATEMENT_UNDER_TEST | |||
| ) |
| #define BSLS_ASSERTTEST_CAN_CHECK_LEVELS |
| #define BSLS_ASSERTTEST_CHECK_LEVEL_ARG false |
| #define BSLS_ASSERTTEST_DISABLED_IMP | ( | STATEMENT_UNDER_TEST | ) |
| #define BSLS_ASSERTTEST_EVAL_STATEMENT_IMP | ( | STATEMENT_UNDER_TEST | ) | STATEMENT_UNDER_TEST; |
| #define BSLS_ASSERTTEST_IS_ACTIVE | ( | TYPE | ) |
| #define BSLS_ASSERTTEST_RECURSIVELY_INCLUDED_TESTDRIVER_GUARD |
| #define BSLS_ASSERTTEST_SAFE_2_BUILD_FLAG false |
| #define BSLS_ASSERTTEST_VALIDATE_DISABLED_MACROS |
| typedef bsls::AssertTest bsls_AssertTest |
This alias is defined for backward compatibility.
This alias is defined for backward compatibility.