Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR contains new unit testing framework as part of the work to refactor the test codes to make them faster (see #4014).
Features
This PR adds new unit testing framework in
<pj/unittest.h>
andpj/unittest.c
. Some features of the framework are:PJ_TEST_XXX()
macros can be used in place of manual testing and error reportingUsing the test macros
At minimum, the
<pj/unittest.h>
provides many useful testing macros that can be used directly to replace manual checking in test apps, without having to use the framework. Test macros currently implemented are:PJ_TEST_SUCCESS(expr, err_reason, err_action)
PJ_TEST_NON_ZERO(expr, err_reason, err_action)
PJ_TEST_TRUE(expr, err_reason, err_action)
PJ_TEST_NOT_NULL(expr, err_reason, err_action)
PJ_TEST_EQ(expr0, expr1, err_reason, err_action)
PJ_TEST_NEQ(expr0, expr1, err_reason, err_action)
PJ_TEST_LT(expr0, expr1, err_reason, err_action)
PJ_TEST_LTE(expr0, expr1, err_reason, err_action)
PJ_TEST_GT(expr0, expr1, err_reason, err_action)
PJ_TEST_GTE(expr0, expr1, err_reason, err_action)
PJ_TEST_STRCMP(ps0, ps1, res_op, exp_result, err_reason, err_action)
PJ_TEST_STRICMP(ps0, ps1, res_op, exp_result, err_reason, err_action)
PJ_TEST_STREQ(ps0, ps1, err_reason, err_action)
PJ_TEST_STRNEQ(ps0, ps1, err_reason, err_action)
For an example:
when
recv_count!=COUNT
will write something like this to the log:as well as executing the code in
err_action
.Introduction to unit test framework
As mentioned above, the framework uses common architecture as described in https://en.wikipedia.org/wiki/XUnit. Basically the architecture is as follows.
Individual test is called a test case, and basically it wraps a test function into
pj_test_case
structure, which you can set many options on it.The test cases are then put into a test suite,
pj_test_suite
. A test application (such as pjsip test) usually only needs one test suite, although pjlib-test has two test suites, essential and features, because it cannot run more complex unit-testing before the unit-test framework itself has been unit-tested!Then we need a runner to run the tests. The framework provides two types of runners, basic runner and text runner. Basic runner simply runs the tests sequentially. In fact, we don't need pool and OS features (such as threads, mutexes, TLS) to create test cases, test suites, and run the test with the basic runner. And this is basically the idea when splitting pjlib unit tests into essential and features. The essential tests is unit-testing using basic runner, to test all features that are needed by the full unit-test framework, such as threads, mutexes, TLS, fifobuf, etc.
The text runner is a multithreaded test runner. This is the only difference from basic runner. Other than this, both runners provides the same functionality and share the same API.
Using the framework
Below is a working sample:
When run, it should produce something like this:
fifobuf
developmentThis PR also contains modifications to
fifobuf.[hc]
, a feature that is part of pjsip initial commit but yet has not find any use until now! The fifobuf is used to save log messages.Known issues
Wrong logging in multithreaded scenario
Logging is saved in buffer to be printed at the end of the test (controllable). The framework does this by saving the current test case to TLS (thread local storage), and retrieve it from the logging callback. This has couple of drawbacks.
First, and this is rather major one, in software architecture where there is a central event dispatcher (like in PJSIP), multiple threads can poll the event dispatcher and any threads can process event that "belongs" to other thread. This will cause logging to be sent to the wrong test case.
In the current development of pjsip test (on separate branch), the "workaround" is to print this message to the screen:
The second drawback, not so major one, when logging is sent by a thread that is not in the context of executing a test case (such as loop transport's worker thread), the framework will not find associated test case with the thread, and hence will just display the log immediately (rather than save it). It will be difficult to associate which test case with the message and at the very least, output will be cluttered.
Note that the test results are correct regardless of the logging. When debugging a test failure, you can disable worker thread (
pj_test_runner_param.nthreads=0
) and log caching (PJ_TEST_LOG_NO_CACHE
) in order to get the correct logging.No nested unit testing
You cannot run a nested unit testing from inside a test case function. The main reason is because logging can only be owned by one test case for each thread.