From c817c214033481fe59f9f44c325a9092dc337d07 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 20 Feb 2014 13:50:19 -0500 Subject: build: GlusterFS Unit Test Framework This patch will allow for developers to create unit tests for their code. Documentation has been added to the patch and is available here: doc/hacker-guide/en-US/markdown/unittest.md Also, unit tests are run when RPM is created. BUG: 1067059 Change-Id: I95cf8bb0354d4ca4ed4476a0f2385436a17d2369 Signed-off-by: Vijay Bellur Signed-off-by: Luis Pabon Reviewed-on: http://review.gluster.org/7145 Tested-by: Gluster Build System Reviewed-by: Rajesh Joseph Reviewed-by: Justin Clift Tested-by: Justin Clift --- doc/hacker-guide/en-US/markdown/unittest.md | 227 ++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 doc/hacker-guide/en-US/markdown/unittest.md (limited to 'doc') diff --git a/doc/hacker-guide/en-US/markdown/unittest.md b/doc/hacker-guide/en-US/markdown/unittest.md new file mode 100644 index 000000000..65e8b85d5 --- /dev/null +++ b/doc/hacker-guide/en-US/markdown/unittest.md @@ -0,0 +1,227 @@ +# Unit Tests in GlusterFS + +## Overview +[Art-of-unittesting][definitionofunittest] provides a good definition for unit tests. A good unit test is: + +* Able to be fully automated +* Has full control over all the pieces running (Use mocks or stubs to achieve this isolation when needed) +* Can be run in any order if part of many other tests +* Runs in memory (no DB or File access, for example) +* Consistently returns the same result (You always run the same test, so no random numbers, for example. save those for integration or range tests) +* Runs fast +* Tests a single logical concept in the system +* Readable +* Maintainable +* Trustworthy (when you see its result, you don’t need to debug the code just to be sure) + +## Cmockery2 +GlusterFS unit test framework is based on [Cmockery2][]. Cmockery provides developers with methods to isolate and test modules written in C language. It also provides integration with Jenkins by providing JUnit XML compliant unit test results. + +Before continuing, you may want to familiarize yourself with Cmockery2 by reading the [usage guide][cmockery2usage]. + +## Running Unit Tests +To execute the unit tests, all you need is to type `make check`. Here is a step-by-step example assuming you just cloned a GlusterFS tree: + +``` +$ ./autogen.sh +$ ./configure --enable-debug +$ make check +``` + +Sample output: + +``` +PASS: mem_pool_unittest +============================================================================ +Testsuite summary for glusterfs 3git +============================================================================ +# TOTAL: 1 +# PASS: 1 +# SKIP: 0 +# XFAIL: 0 +# FAIL: 0 +# XPASS: 0 +# ERROR: 0 +============================================================================ +``` + +In this example, `mem_pool_unittest` has multiple tests inside, but `make check` assumes that the program itself is the test, and that is why it only shows one test. Here is the output when we run `mem_pool_unittest` directly: + +``` +$ ./libglusterfs/src/mem_pool_unittest +[==========] Running 10 test(s). +[ RUN ] test_gf_mem_acct_enable_set +Expected assertion data != ((void *)0) occurred +[ OK ] test_gf_mem_acct_enable_set +[ RUN ] test_gf_mem_set_acct_info_asserts +Expected assertion xl != ((void *)0) occurred +Expected assertion size > ((4 + sizeof (size_t) + sizeof (xlator_t *) + 4 + 8) + 8) occurred +Expected assertion type <= xl->mem_acct.num_types occurred +[ OK ] test_gf_mem_set_acct_info_asserts +[ RUN ] test_gf_mem_set_acct_info_memory +[ OK ] test_gf_mem_set_acct_info_memory +[ RUN ] test_gf_calloc_default_calloc +[ OK ] test_gf_calloc_default_calloc +[ RUN ] test_gf_calloc_mem_acct_enabled +[ OK ] test_gf_calloc_mem_acct_enabled +[ RUN ] test_gf_malloc_default_malloc +[ OK ] test_gf_malloc_default_malloc +[ RUN ] test_gf_malloc_mem_acct_enabled +[ OK ] test_gf_malloc_mem_acct_enabled +[ RUN ] test_gf_realloc_default_realloc +[ OK ] test_gf_realloc_default_realloc +[ RUN ] test_gf_realloc_mem_acct_enabled +[ OK ] test_gf_realloc_mem_acct_enabled +[ RUN ] test_gf_realloc_ptr +Expected assertion ((void *)0) != ptr occurred +[ OK ] test_gf_realloc_ptr +[==========] 10 test(s) run. +[ PASSED ] 10 test(s). +[ FAILED ] 0 test(s). +[ REPORT ] Created libglusterfs_mem_pool_xunit.xml report +``` + + +## Writing Unit Tests + +### Enhancing your C functions + +#### Programming by Contract +Add the following to your C file: + +```c +#include +``` + +```c +/* + * Programming by Contract is a programming methodology + * which binds the caller and the function called to a + * contract. The contract is represented using Hoare Triple: + * {P} C {Q} + * where {P} is the precondition before executing command C, + * and {Q} is the postcondition. + * + * See also: + * http://en.wikipedia.org/wiki/Design_by_contract + * http://en.wikipedia.org/wiki/Hoare_logic + * http://dlang.org/dbc.html + */ + #ifndef CMOCKERY_PBC_H_ +#define CMOCKERY_PBC_H_ + +#if defined(UNIT_TESTING) || defined (DEBUG) + +#include + +/* + * Checks caller responsability against contract + */ +#define REQUIRE(cond) assert(cond) + +/* + * Checks function reponsability against contract. + */ +#define ENSURE(cond) assert(cond) + +/* + * While REQUIRE and ENSURE apply to functions, INVARIANT + * applies to classes/structs. It ensures that intances + * of the class/struct are consistant. In other words, + * that the instance has not been corrupted. + */ +#define INVARIANT(invariant_fnc) do{ (invariant_fnc) } while (0); + +#else +#define REQUIRE(cond) do { } while (0); +#define ENSURE(cond) do { } while (0); +#define INVARIANT(invariant_fnc) do{ } while (0); + +#endif /* defined(UNIT_TESTING) || defined (DEBUG) */ +#endif /* CMOCKERY_PBC_H_ */ +``` + +##### Example +This is an _extremely_ simple example: + +```c +int divide (int n, int d) +{ + int ans; + + REQUIRE(d != 0); + + ans = n / d; + + // As code is added to this function throughout its lifetime, + // ENSURE will assert that data will be returned + // according to the contract. Again this is an + // extremely simple example. :-D + ENSURE( ans == (n / d) ); + + return ans; +} + +``` + +##### Important Note +`REQUIRE`, `ENSURE`, and `INVARIANT` are only available when `DEBUG` or `UNIT_TESTING` are set in the CFLAGS. You must pass `--enable-debug` to `./configure` to enable PBC on your non-unittest builds. + +#### Overriding functions +Cmockery2 provides its own memory allocation functions which check for buffer overrun and memory leaks. The following header file must be included **last** to be able to override any of the memory allocation functions: + +```c +#include +``` + +This file will only take effect with the `UNIT_TESTING` CFLAG is set. + +### Creating a unit test +Once you identify the C file you would like to test, first create a `unittest` directory under the directory where the C file is located. This will isolate the unittests to a different directory. + +Next, you need to edit the `Makefile.am` file in the directory where your C file is located. Initialize the +`Makefile.am` if it does not already have the following sections: + +``` +#### UNIT TESTS ##### +CLEANFILES += *.gcda *.gcno *_xunit.xml +noinst_PROGRAMS = +TESTS = +``` + +Now you can add the following for each of the unit tests that you would like to build: + +``` +### UNIT TEST xxx_unittest ### +xxx_unittest_CPPFLAGS = $(UNITTEST_CPPFLAGS) $(xxx_CPPFLAGS) +xxx_unittest_SOURCES = xxx.c \ + unittest/xxx_unittest.c +xxx_unittest_CFLAGS = $(UNITTEST_CFLAGS) +xxx_unittest_LDADD = $(UNITTEST_LDADD) +xxx_unittest_LDFLAGS = $(UNITTEST_LDFLAGS) +noinst_PROGRAMS += xxx_unittest +TESTS += xxx_unittest +``` + +Where `xxx` is the name of your C file. For example, look at `libglusterfs/src/Makefile.am`. + +Copy the simple unit test from `cmockery2/src/example/run_tests.c` to `unittest/xxx_unittest.c`. If you would like to see an example of a unit test, please refer to `libglusterfs/src/unittest/mem_pool_unittest.c`. + +#### Mocking +You may see that the linker will complain about missing functions needed by the C file you would like to test. Identify the required functions, then place their stubs in a file called `unittest/xxx_mock.c`, then include this file in `Makefile.am` in `xxx_unittest_SOURCES`. This will allow you to you Cmockery2's mocking functions. + +#### Running the unit test +You can type `make` in the directory where the C file is located. Once you built it and there are no errors, you can execute the test either by directly executing the program (in our example above it is called `xxx_unittest` ), or by running `make check`. + +#### Debugging +Sometimes you may need to debug your unit test. To do that, you will have to point `gdb` to the actual binary which is located in the `.libs` subdirectory. For example, you can do the following from the root of the source tree to debug `mem_pool_unittest`: + +``` +$ export LD_LIBRARY_PATH=cmockery2/.libs +$ gdb ./libglusterfs/src/.libs/mem_pool_unittest +``` + + +[Cmockery2]: https://github.com/lpabon/cmockery2 +[definitionofunittest]: http://artofunittesting.com/definition-of-a-unit-test/ +[cmockery2usage]: https://github.com/lpabon/cmockery2/blob/master/doc/usage.md -- cgit