Thursday, September 24, 2015

Unit-Testing+Mocking Nginx Module

The following are the frameworks that I looked into to finalize the one which is the most suitable for unit testing+mocking module in nginx.

  • unity + cmock
    • unit testing was almost okay (have to use ruby to generate test runner)
    • for mocking you need to extract all the functions that you need to mock into a new header file. The framework would then generate a mocked version to header files that can be used. The problem here is the extraction of functions to be mocked requires lots of time and patience.
  • gTest  +  gMock
    • unit testing was perfect, uses macro to register test ;)
    • the problem with mocking free functions is that you need to rewrite your code to use interface.
  • cmocka
    • unit testing was okay (ease of use : between google's and unity's)
    • for mocking all u need to do write the mocked-version of the function that you need to mock. Framework utilizes that wrap feature of linker.


The following is the way how I used cmocka to unit-test+mock the nginx module : 

Folder structure
--src
|-> nginx-1.8.0 [nginx source]
|
|-> nginx_module
|  |-> ngx_redis_module.c
|
|-> test
 |-> test_ngx_redis_module.c
 |-> test_ngx_redis_module.h
 |-> makefile

The function that I need to unit test is the ngx_redis_event_handler() in the ngx_redis_module.c. This function calls two other functions redisAsyncHandleWrite() and redisAsyncHandleRead(), since these functions are not under test it has to be mocked. As the framework uses wrap feature of linker for mocking, you need to define the mocked definition of these two functions with __wrap_ as the prefix for the mocked-function's name (see : test_ngx_redis_module.h ). Now declare the mocked-functions in the test file (see : test_ngx_redis_module.c) and write test for the function to be tested (refer the cmocka on how to write tests). 

In the example I have written three tests for unit-testing ngx_redis_event_handler().
  • test_ngx_redis_event_handler_write() : since line#22 and #23 of the function are executed you need to create the objects necessary to run them without exception. This test sets the ev->write = 1, so it flows through if() in line#25 of the function being tested, where the mocked version of redisAsyncHandleWrite() i.e __wrap_redisAsyncHandleWrite() is called at line#27 
  • test_ngx_redis_event_handler_read() : since line#22 and #23 of the function are executed you need to create the objects necessary to run them without exception. This test sets the ev->write = 0, so it flows through else in line#28 of the function being tested, where the mocked version of redisAsyncHandleRead() i.e __wrap_redisAsyncHandleRead() is called at line#30
  • test_ngx_redis_event_handler_ready : since this test enters the if() at line#18 and returns, line#22 to #33 won't be executed hence objects required may not be created.
Use expect_* and check_* to verify that the mocked functions are called.

Now to compile the test cases you need to include all the objects files that the test and module refers to. This can be found from the makefile that nginx uses to compile the nginx.o, located at 'nginx-1.8.0/objs/Makefile' under the section 'objs/nginx:' (everything between  '$(LINK) -o' and 'objs/ngx_modules.o'(included)). Make sure you remove the objects file of the nginx module (ngx_redis_module.o) from the above list of object files, it is included in src/test/makefile. 

The second thing you need to do is to remove 'main()' from the 'nginx.o', else ld will state that you have two mains. Use the 'strip' command to remove main from nginx.o (located at nginx-1.8.0/objs/src/core)

$> strip -N main -o nginx_without_main.o nginx.o

replace the nginx.o with nginx_without_main.o in your makefile that you use to compile the test.

Now, 'make run' to run the tests.

[==========] Running 3 test(s).
[ RUN           ] test_ngx_redis_event_handler_write
[             OK  ] test_ngx_redis_event_handler_write
[ RUN           ] test_ngx_redis_event_handler_read
[             OK  ] test_ngx_redis_event_handler_read
[ RUN           ] test_ngx_redis_event_handler_ready
[             OK  ] test_ngx_redis_event_handler_ready
[==========] 3 test(s) run.
[  PASSED     ] 3 test(s).