diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | Makefile.am | 2 | ||||
| -rwxr-xr-x | autogen.sh | 9 | ||||
| m--------- | cmockery2 | 0 | ||||
| -rw-r--r-- | configure.ac | 14 | ||||
| -rw-r--r-- | doc/hacker-guide/en-US/markdown/unittest.md | 227 | ||||
| -rw-r--r-- | glusterfs.spec.in | 7 | ||||
| -rw-r--r-- | libglusterfs/src/Makefile.am | 17 | ||||
| -rw-r--r-- | libglusterfs/src/mem-pool.c | 39 | ||||
| -rw-r--r-- | libglusterfs/src/mem-pool.h | 13 | ||||
| -rw-r--r-- | libglusterfs/src/unittest/global_mock.c | 24 | ||||
| -rw-r--r-- | libglusterfs/src/unittest/log_mock.c | 43 | ||||
| -rw-r--r-- | libglusterfs/src/unittest/mem_pool_unittest.c | 472 | ||||
| -rw-r--r-- | xlators/cluster/dht/src/Makefile.am | 15 | ||||
| -rw-r--r-- | xlators/cluster/dht/src/dht-layout.c | 20 | ||||
| -rw-r--r-- | xlators/cluster/dht/src/unittest/dht_layout_mock.c | 63 | ||||
| -rw-r--r-- | xlators/cluster/dht/src/unittest/dht_layout_unittest.c | 124 | ||||
| -rw-r--r-- | xlators/storage/posix/src/posix-helpers.c | 10 | 
19 files changed, 1087 insertions, 17 deletions
diff --git a/.gitignore b/.gitignore index 1649aa6fba0..e8d0012aa06 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ ltmain.sh  Makefile.in  missing  py-compile +*.gcda +*.gcno  *.sw?  *~  *.lo diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..58165fe0a7d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cmockery2"] +	path = cmockery2 +	url = https://github.com/lpabon/cmockery2.git diff --git a/Makefile.am b/Makefile.am index 598ebb4103c..815d6ad3e5f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,7 +6,7 @@ EXTRA_DIST = autogen.sh \  	gen-headers.py run-tests.sh \  	$(shell find $(top_srcdir)/tests -type f -print) -SUBDIRS = argp-standalone libglusterfs rpc api xlators glusterfsd \ +SUBDIRS = argp-standalone cmockery2 libglusterfs rpc api xlators glusterfsd \  	$(FUSERMOUNT_SUBDIR) doc extras cli @SYNCDAEMON_SUBDIR@  pkgconfigdir = @pkgconfigdir@ diff --git a/autogen.sh b/autogen.sh index cd8603b66d3..221cc2b7819 100755 --- a/autogen.sh +++ b/autogen.sh @@ -106,8 +106,15 @@ $AUTOCONF  echo Running ${AUTOMAKE}...  $AUTOMAKE --add-missing --copy --foreign +# Update git modules +echo "Obtaining git module cmockery2 ..." +git submodule update --init cmockery2 +  # Run autogen in the argp-standalone sub-directory -cd argp-standalone;./autogen.sh +echo "Running autogen.sh in argp-standalone ..." +( cd argp-standalone;./autogen.sh ) +echo "Running autogen.sh in cmockery2 ..." +( cd cmockery2; ./autogen.sh )  # Instruct user on next steps  echo diff --git a/cmockery2 b/cmockery2 new file mode 160000 +Subproject 4eb53ab96fd2b227fbf68c40bcc8d915b48213b diff --git a/configure.ac b/configure.ac index 69d25e4dad7..626f6f6289c 100644 --- a/configure.ac +++ b/configure.ac @@ -630,6 +630,18 @@ AC_SUBST(HAVE_LINKAT)  dnl check for Monotonic clock  AC_CHECK_FUNC([clock_gettime], [has_monotonic_clock=yes], AC_CHECK_LIB([rt], [clock_gettime], , AC_MSG_WARN([System doesn't have monotonic clock using contrib]))) +dnl Add cmockery2 for unit testing +AC_CONFIG_SUBDIRS([cmockery2]) +UNITTEST_CFLAGS='-g -Wall -DUNIT_TESTING=1 -DDEBUG -Werror -O0 --coverage' +UNITTEST_CPPFLAGS='-I$(top_srcdir)/cmockery2/src' +UNITTEST_LDADD='$(top_builddir)/cmockery2/libcmockery.la' +UNITTEST_LDFLAGS=-lgcov +CFLAGS="$CFLAGS ${UNITTEST_CPPFLAGS}" +AC_SUBST(UNITTEST_CFLAGS) +AC_SUBST(UNITTEST_CPPFLAGS) +AC_SUBST(UNITTEST_LDADD) +AC_SUBST(UNITTEST_LDFLAGS) +  dnl Check for argp  AC_CHECK_HEADER([argp.h], AC_DEFINE(HAVE_ARGP, 1, [have argp]))  AC_CONFIG_SUBDIRS(argp-standalone) @@ -900,7 +912,7 @@ CONTRIBDIR='$(top_srcdir)/contrib'  AC_SUBST(CONTRIBDIR)  GF_CPPDEFINES='-D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D$(GF_HOST_OS)' -GF_CPPINCLUDES='-I$(top_srcdir)/libglusterfs/src -I$(CONTRIBDIR)/uuid' +GF_CPPINCLUDES='-I$(top_srcdir)/libglusterfs/src -I$(CONTRIBDIR)/uuid $(UNITTEST_CPPFLAGS)'  GF_CPPFLAGS="$GF_CPPDEFINES $GF_CPPINCLUDES"  AC_SUBST([GF_CPPFLAGS]) 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 00000000000..65e8b85d55c --- /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 <cmockery/pbc.h> +``` + +```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 <assert.h> + +/* + * 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 <cmockery/cmockery_override.h> +``` + +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 diff --git a/glusterfs.spec.in b/glusterfs.spec.in index 43662b5a9ef..5e661399f28 100644 --- a/glusterfs.spec.in +++ b/glusterfs.spec.in @@ -450,6 +450,10 @@ pushd api/examples  FLAGS="$RPM_OPT_FLAGS" python setup.py build  popd +%check + +LD_LIBRARY_PATH=$PWD/cmockery2/.libs make check +  %install  rm -rf %{buildroot}  make install DESTDIR=%{buildroot} @@ -666,6 +670,9 @@ rm -rf %{buildroot}  %exclude %{_libdir}/glusterfs/%{version}%{?prereltag}/xlator/encryption/rot-13*  %exclude %{_libdir}/glusterfs/%{version}%{?prereltag}/xlator/features/mac-compat*  %exclude %{_libdir}/glusterfs/%{version}%{?prereltag}/xlator/testing/performance/symlink-cache* +# exclude cmockery +%exclude %{_includedir}/cmockery* +%exclude %{_prefix}/share/doc/cmockery*  %post libs  /sbin/ldconfig diff --git a/libglusterfs/src/Makefile.am b/libglusterfs/src/Makefile.am index 634e217ed4d..ac6a3b9ecde 100644 --- a/libglusterfs/src/Makefile.am +++ b/libglusterfs/src/Makefile.am @@ -55,3 +55,20 @@ y.tab.h: graph.y  CLEANFILES = graph.lex.c y.tab.c y.tab.h  CONFIG_CLEAN_FILES = $(CONTRIB_BUILDDIR)/uuid/uuid_types.h + +#### UNIT TESTS ##### +CLEANFILES += *.gcda *.gcno *_xunit.xml +noinst_PROGRAMS = +TESTS = + +mem_pool_unittest_CPPFLAGS = $(UNITTEST_CPPFLAGS) $(libglusterfs_la_CPPFLAGS) +mem_pool_unittest_SOURCES = mem-pool.c \ +							mem-pool.h \ +							unittest/mem_pool_unittest.c \ +							unittest/log_mock.c \ +							unittest/global_mock.c +mem_pool_unittest_CFLAGS = $(UNITTEST_CFLAGS) +mem_pool_unittest_LDADD = $(UNITTEST_LDADD) +mem_pool_unittest_LDFLAGS = $(UNITTEST_LDFLAGS) +noinst_PROGRAMS += mem_pool_unittest +TESTS += mem_pool_unittest diff --git a/libglusterfs/src/mem-pool.c b/libglusterfs/src/mem-pool.c index b901dd7a862..96e04910513 100644 --- a/libglusterfs/src/mem-pool.c +++ b/libglusterfs/src/mem-pool.c @@ -22,29 +22,30 @@  #define is_mem_chunk_in_use(ptr)         (*ptr == 1)  #define mem_pool_from_ptr(ptr)           ((ptr) + GF_MEM_POOL_LIST_BOUNDARY) -#define GF_MEM_HEADER_SIZE  (4 + sizeof (size_t) + sizeof (xlator_t *) + 4 + 8) -#define GF_MEM_TRAILER_SIZE 8 - -#define GF_MEM_HEADER_MAGIC  0xCAFEBABE -#define GF_MEM_TRAILER_MAGIC 0xBAADF00D -  #define GLUSTERFS_ENV_MEM_ACCT_STR  "GLUSTERFS_DISABLE_MEM_ACCT" +#include <cmockery/pbc.h> +#include <cmockery/cmockery_override.h> +  void  gf_mem_acct_enable_set (void *data)  {          glusterfs_ctx_t *ctx = NULL; +        REQUIRE(data != NULL); +          ctx = data; -        GF_ASSERT (ctx); +        GF_ASSERT (ctx != NULL);          ctx->mem_acct_enable = 1; +        ENSURE(1 == ctx->mem_acct_enable); +          return;  } -void +int  gf_mem_set_acct_info (xlator_t *xl, char **alloc_ptr,                        size_t size, uint32_t type)  { @@ -52,7 +53,7 @@ gf_mem_set_acct_info (xlator_t *xl, char **alloc_ptr,          char    *ptr = NULL;          if (!alloc_ptr) -                return; +                return -1;          ptr = (char *) (*alloc_ptr); @@ -88,7 +89,7 @@ gf_mem_set_acct_info (xlator_t *xl, char **alloc_ptr,          *(uint32_t *) (ptr + size) = GF_MEM_TRAILER_MAGIC;          *alloc_ptr = (void *)ptr; -        return; +        return 0;  } @@ -150,10 +151,13 @@ __gf_realloc (void *ptr, size_t size)          char            *orig_ptr = NULL;          xlator_t        *xl = NULL;          uint32_t        type = 0; +        char            *new_ptr;          if (!THIS->ctx->mem_acct_enable)                  return REALLOC (ptr, size); +        REQUIRE(NULL != ptr); +          tot_size = size + GF_MEM_HEADER_SIZE + GF_MEM_TRAILER_SIZE;          orig_ptr = (char *)ptr - 8 - 4; @@ -166,15 +170,22 @@ __gf_realloc (void *ptr, size_t size)          orig_ptr = (char *)ptr - GF_MEM_HEADER_SIZE;          type = *(uint32_t *)orig_ptr; -        ptr = realloc (orig_ptr, tot_size); -        if (!ptr) { +        new_ptr = realloc (orig_ptr, tot_size); +        if (!new_ptr) {                  gf_log_nomem ("", GF_LOG_ALERT, tot_size);                  return NULL;          } -        gf_mem_set_acct_info (xl, (char **)&ptr, size, type); +        /* +         * We used to pass (char **)&ptr as the second +         * argument after the value of realloc was saved +         * in ptr, but the compiler warnings complained +         * about the casting to and forth from void ** to +         * char **. +         */ +        gf_mem_set_acct_info (xl, &new_ptr, size, type); -        return (void *)ptr; +        return (void *)new_ptr;  }  int diff --git a/libglusterfs/src/mem-pool.h b/libglusterfs/src/mem-pool.h index 31f49f75cbb..aa6bf784363 100644 --- a/libglusterfs/src/mem-pool.h +++ b/libglusterfs/src/mem-pool.h @@ -20,6 +20,19 @@  #include <string.h>  #include <stdarg.h> +/* + * Need this for unit tests since inline functions + * access memory allocation and need to use the + * unit test versions + */ +#ifdef UNIT_TESTING +#include <cmockery/cmockery_override.h> +#endif + +#define GF_MEM_HEADER_SIZE  (4 + sizeof (size_t) + sizeof (xlator_t *) + 4 + 8) +#define GF_MEM_TRAILER_SIZE 8 +#define GF_MEM_HEADER_MAGIC  0xCAFEBABE +#define GF_MEM_TRAILER_MAGIC 0xBAADF00D  struct mem_acct {          uint32_t            num_types; diff --git a/libglusterfs/src/unittest/global_mock.c b/libglusterfs/src/unittest/global_mock.c new file mode 100644 index 00000000000..b50638d1023 --- /dev/null +++ b/libglusterfs/src/unittest/global_mock.c @@ -0,0 +1,24 @@ +/* +  Copyright (c) 2014 Red Hat, Inc. <http://www.redhat.com> +  This file is part of GlusterFS. + +  This file is licensed to you under your choice of the GNU Lesser +  General Public License, version 3 or any later version (LGPLv3 or +  later), or the GNU General Public License, version 2 (GPLv2), in all +  cases as published by the Free Software Foundation. +*/ + +#include "logging.h" +#include "xlator.h" + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <inttypes.h> + +#include <cmockery/cmockery.h> + +xlator_t **__glusterfs_this_location () +{ +    return ((xlator_t **)(uintptr_t)mock()); +} diff --git a/libglusterfs/src/unittest/log_mock.c b/libglusterfs/src/unittest/log_mock.c new file mode 100644 index 00000000000..676df7cfdad --- /dev/null +++ b/libglusterfs/src/unittest/log_mock.c @@ -0,0 +1,43 @@ +/* +  Copyright (c) 2014 Red Hat, Inc. <http://www.redhat.com> +  This file is part of GlusterFS. + +  This file is licensed to you under your choice of the GNU Lesser +  General Public License, version 3 or any later version (LGPLv3 or +  later), or the GNU General Public License, version 2 (GPLv2), in all +  cases as published by the Free Software Foundation. +*/ + +#include "logging.h" +#include "xlator.h" + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <inttypes.h> + +#include <cmockery/cmockery.h> + +int _gf_log (const char *domain, const char *file, +             const char *function, int32_t line, gf_loglevel_t level, +             const char *fmt, ...) +{ +    return 0; +} + +int _gf_log_callingfn (const char *domain, const char *file, +                       const char *function, int32_t line, gf_loglevel_t level, +                       const char *fmt, ...) +{ +    return 0; +} + +int _gf_log_nomem (const char *domain, const char *file, +                   const char *function, int line, gf_loglevel_t level, +                   size_t size) +{ +    return 0; +} + +void +gf_log_globals_init (void *data) {} diff --git a/libglusterfs/src/unittest/mem_pool_unittest.c b/libglusterfs/src/unittest/mem_pool_unittest.c new file mode 100644 index 00000000000..3c0724d65e5 --- /dev/null +++ b/libglusterfs/src/unittest/mem_pool_unittest.c @@ -0,0 +1,472 @@ +/* +  Copyright (c) 2014 Red Hat, Inc. <http://www.redhat.com> +  This file is part of GlusterFS. + +  This file is licensed to you under your choice of the GNU Lesser +  General Public License, version 3 or any later version (LGPLv3 or +  later), or the GNU General Public License, version 2 (GPLv2), in all +  cases as published by the Free Software Foundation. +*/ + +#include "mem-pool.h" +#include "logging.h" +#include "xlator.h" + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <inttypes.h> +#include <string.h> +#include <cmockery/pbc.h> +#include <cmockery/cmockery.h> + +/* + * memory header for gf_mem_set_acct_info + */ +typedef struct __attribute__((packed)) { +    uint32_t type; +    size_t size; +    xlator_t *xl; +    uint32_t header_magic; +    uint8_t pad[8]; +} mem_header_t; + +/* + * Prototypes to private functions + */ +int +gf_mem_set_acct_info (xlator_t *xl, char **alloc_ptr, +                      size_t size, uint32_t type); + +/* + * Helper functions + */ +static xlator_t * +helper_xlator_init(uint32_t num_types) +{ +    xlator_t *xl; +    int i, ret; + +    REQUIRE(num_types > 0); + +    xl = test_calloc(1, sizeof(xlator_t)); +    assert_non_null(xl); +    xl->mem_acct.num_types = num_types; +    xl->mem_acct.rec = test_calloc(num_types, sizeof(struct mem_acct_rec)); +    assert_non_null(xl->mem_acct.rec); + +    xl->ctx = test_calloc(1, sizeof(glusterfs_ctx_t)); +    assert_non_null(xl->ctx); + +    for (i = 0; i < num_types; i++) { +            ret = LOCK_INIT(&(xl->mem_acct.rec[i].lock)); +            assert_int_equal(ret, 0); +    } + +    ENSURE(num_types == xl->mem_acct.num_types); +    ENSURE(NULL != xl); + +    return xl; +} + +static int +helper_xlator_destroy(xlator_t *xl) +{ +    int i, ret; + +    for (i = 0; i < xl->mem_acct.num_types; i++) { +            ret = LOCK_DESTROY(&(xl->mem_acct.rec[i].lock)); +            assert_int_equal(ret, 0); +    } + +    free(xl->mem_acct.rec); +    free(xl->ctx); +    free(xl); +    return 0; +} + +static void +helper_check_memory_headers( char *mem, +        xlator_t *xl, +        size_t size, +        uint32_t type) +{ +    mem_header_t *p; + +    p = (mem_header_t *)mem, +    assert_int_equal(p->type, type); +    assert_int_equal(p->size, size); +    assert_true(p->xl == xl); +    assert_int_equal(p->header_magic, GF_MEM_HEADER_MAGIC); +    assert_true(*(uint32_t *)(mem+sizeof(mem_header_t)+size) == GF_MEM_TRAILER_MAGIC); + +} + +/* + * Tests + */ +static void +test_gf_mem_acct_enable_set(void **state) +{ +    (void) state; +    glusterfs_ctx_t test_ctx; + +    expect_assert_failure(gf_mem_acct_enable_set(NULL)); + +    memset(&test_ctx, 0, sizeof(test_ctx)); +    assert_true(NULL == test_ctx.process_uuid); +    gf_mem_acct_enable_set((void *)&test_ctx); +    assert_true(1 == test_ctx.mem_acct_enable); +    assert_true(NULL == test_ctx.process_uuid); +} + +static void +test_gf_mem_set_acct_info_asserts(void **state) +{ +    xlator_t *xl; +    xlator_t xltest; +    char *alloc_ptr; +    size_t size; +    uint32_t type; + +    memset(&xltest, 0, sizeof(xlator_t)); +    xl = (xlator_t *)0xBADD; +    alloc_ptr = (char *)0xBADD; +    size = 8196; +    type = 0; + + +    // Check xl is NULL +    expect_assert_failure(gf_mem_set_acct_info(NULL, &alloc_ptr, size, type)); +    // Check xl->mem_acct.rec = NULL +    expect_assert_failure(gf_mem_set_acct_info(&xltest, &alloc_ptr, 0, type)); +    // Check type <= xl->mem_acct.num_types +    type = 100; +    expect_assert_failure(gf_mem_set_acct_info(&xltest, &alloc_ptr, 0, type)); +    // Check alloc is NULL +    assert_int_equal(-1, gf_mem_set_acct_info(&xltest, NULL, size, type)); + +    // Initialize xl +    xl = helper_xlator_init(10); + +    // Test number of types +    type = 100; +    assert_true(NULL != xl->mem_acct.rec); +    assert_true(type > xl->mem_acct.num_types); +    expect_assert_failure(gf_mem_set_acct_info(xl, &alloc_ptr, size, type)); + +    helper_xlator_destroy(xl); +} + +static void +test_gf_mem_set_acct_info_memory(void **state) +{ +    xlator_t *xl; +    char *alloc_ptr; +    char *temp_ptr; +    size_t size; +    uint32_t type; + +    size = 8196; +    type = 9; + +    // Initialize xl +    xl = helper_xlator_init(10); + +    // Test allocation +    temp_ptr = test_calloc(1, size + GF_MEM_HEADER_SIZE + GF_MEM_TRAILER_SIZE); +    assert_non_null(temp_ptr); +    alloc_ptr = temp_ptr; +    gf_mem_set_acct_info(xl, &alloc_ptr, size, type); + +    //Check values +    assert_int_equal(xl->mem_acct.rec[type].size, size); +    assert_int_equal(xl->mem_acct.rec[type].num_allocs, 1); +    assert_int_equal(xl->mem_acct.rec[type].total_allocs, 1); +    assert_int_equal(xl->mem_acct.rec[type].max_size, size); +    assert_int_equal(xl->mem_acct.rec[type].max_num_allocs, 1); + +    // Check memory +    helper_check_memory_headers(temp_ptr, xl, size, type); + +    // Check that alloc_ptr has been moved correctly +    // by gf_mem_set_acct_info +    { +        mem_header_t *p; + +        p = (mem_header_t *)temp_ptr; +        p++; +        p->type = 1234; +        assert_int_equal(*(uint32_t *)alloc_ptr, p->type); +    } + +    free(temp_ptr); +    helper_xlator_destroy(xl); +} + +static void +test_gf_calloc_default_calloc(void **state) +{ +    xlator_t *xl; +    void *mem; +    size_t size; +    uint32_t type; + +    // Initialize xl +    xl = helper_xlator_init(10); +    assert_int_equal(xl->ctx->mem_acct_enable, 0); +    will_return(__glusterfs_this_location, &xl); + +    // Call __gf_calloc +    size = 1024; +    type = 3; +    mem = __gf_calloc(1, size, type); +    assert_non_null(mem); +    memset(mem, 0x5A, size); + +    // Check xl did not change +    assert_int_equal(xl->mem_acct.rec[type].size, 0); +    assert_int_equal(xl->mem_acct.rec[type].num_allocs, 0); +    assert_int_equal(xl->mem_acct.rec[type].total_allocs, 0); +    assert_int_equal(xl->mem_acct.rec[type].max_size, 0); +    assert_int_equal(xl->mem_acct.rec[type].max_num_allocs, 0); + +    free(mem); +    helper_xlator_destroy(xl); +} + +static void +test_gf_calloc_mem_acct_enabled(void **state) +{ +    xlator_t *xl; +    void *mem; +    size_t size; +    uint32_t type; + +    // Initialize xl +    xl = helper_xlator_init(10); +    assert_int_equal(xl->ctx->mem_acct_enable, 0); +    xl->ctx->mem_acct_enable = 1; + +    // For line mem-pool.c:115 and mem-pool:118 +    will_always_return(__glusterfs_this_location, &xl); + +    // Call __gf_calloc +    size = 1024; +    type = 3; +    mem = __gf_calloc(1, size, type); +    assert_non_null(mem); +    memset(mem, 0x5A, size); + +    // Check xl values +    assert_int_equal(xl->mem_acct.rec[type].size, size); +    assert_int_equal(xl->mem_acct.rec[type].num_allocs, 1); +    assert_int_equal(xl->mem_acct.rec[type].total_allocs, 1); +    assert_int_equal(xl->mem_acct.rec[type].max_size, size); +    assert_int_equal(xl->mem_acct.rec[type].max_num_allocs, 1); + +    // Check memory +    helper_check_memory_headers(mem - sizeof(mem_header_t), xl, size, type); +    free(mem - sizeof(mem_header_t)); +    helper_xlator_destroy(xl); +} + +static void +test_gf_malloc_default_malloc(void **state) +{ +    xlator_t *xl; +    void *mem; +    size_t size; +    uint32_t type; + +    // Initialize xl +    xl = helper_xlator_init(10); +    assert_int_equal(xl->ctx->mem_acct_enable, 0); +    will_return(__glusterfs_this_location, &xl); + +    // Call __gf_malloc +    size = 1024; +    type = 3; +    mem = __gf_malloc(size, type); +    assert_non_null(mem); +    memset(mem, 0x5A, size); + +    // Check xl did not change +    assert_int_equal(xl->mem_acct.rec[type].size, 0); +    assert_int_equal(xl->mem_acct.rec[type].num_allocs, 0); +    assert_int_equal(xl->mem_acct.rec[type].total_allocs, 0); +    assert_int_equal(xl->mem_acct.rec[type].max_size, 0); +    assert_int_equal(xl->mem_acct.rec[type].max_num_allocs, 0); + +    free(mem); +    helper_xlator_destroy(xl); +} + +static void +test_gf_malloc_mem_acct_enabled(void **state) +{ +    xlator_t *xl; +    void *mem; +    size_t size; +    uint32_t type; + +    // Initialize xl +    xl = helper_xlator_init(10); +    assert_int_equal(xl->ctx->mem_acct_enable, 0); +    xl->ctx->mem_acct_enable = 1; + +    // For line mem-pool.c:115 and mem-pool:118 +    will_always_return(__glusterfs_this_location, &xl); + +    // Call __gf_malloc +    size = 1024; +    type = 3; +    mem = __gf_malloc(size, type); +    assert_non_null(mem); +    memset(mem, 0x5A, size); + +    // Check xl values +    assert_int_equal(xl->mem_acct.rec[type].size, size); +    assert_int_equal(xl->mem_acct.rec[type].num_allocs, 1); +    assert_int_equal(xl->mem_acct.rec[type].total_allocs, 1); +    assert_int_equal(xl->mem_acct.rec[type].max_size, size); +    assert_int_equal(xl->mem_acct.rec[type].max_num_allocs, 1); + +    // Check memory +    helper_check_memory_headers(mem - sizeof(mem_header_t), xl, size, type); +    free(mem - sizeof(mem_header_t)); +    helper_xlator_destroy(xl); +} + +static void +test_gf_realloc_default_realloc(void **state) +{ +    xlator_t *xl; +    void *mem; +    size_t size; +    uint32_t type; + +    // Initialize xl +    xl = helper_xlator_init(10); +    assert_int_equal(xl->ctx->mem_acct_enable, 0); +    will_always_return(__glusterfs_this_location, &xl); + +    // Call __gf_malloc then realloc +    size = 10; +    type = 3; +    mem = __gf_malloc(size, type); +    assert_non_null(mem); +    memset(mem, 0xA5, size); + +    size = 1024; +    mem = __gf_realloc(mem, size); +    assert_non_null(mem); +    memset(mem, 0x5A, size); + +    // Check xl did not change +    assert_int_equal(xl->mem_acct.rec[type].size, 0); +    assert_int_equal(xl->mem_acct.rec[type].num_allocs, 0); +    assert_int_equal(xl->mem_acct.rec[type].total_allocs, 0); +    assert_int_equal(xl->mem_acct.rec[type].max_size, 0); +    assert_int_equal(xl->mem_acct.rec[type].max_num_allocs, 0); + +    free(mem); +    helper_xlator_destroy(xl); +} + +static void +test_gf_realloc_mem_acct_enabled(void **state) +{ +    xlator_t *xl; +    void *mem; +    size_t size; +    uint32_t type; + +    // Initialize xl +    xl = helper_xlator_init(10); +    assert_int_equal(xl->ctx->mem_acct_enable, 0); +    xl->ctx->mem_acct_enable = 1; + +    // For line mem-pool.c:115 and mem-pool:118 +    will_always_return(__glusterfs_this_location, &xl); + +    // Call __gf_malloc then realloc +    size = 1024; +    type = 3; +    mem = __gf_malloc(size, type); +    assert_non_null(mem); +    memset(mem, 0xA5, size); + +    size = 2048; +    mem = __gf_realloc(mem, size); +    assert_non_null(mem); +    memset(mem, 0x5A, size); + +    // Check xl values +    // +    // :TODO: This is really weird.  I would have expected +    // xl to only have a size equal to that of the realloc +    // not to the realloc + the malloc. +    // Is this a bug? +    // +    assert_int_equal(xl->mem_acct.rec[type].size, size+1024); +    assert_int_equal(xl->mem_acct.rec[type].num_allocs, 2); +    assert_int_equal(xl->mem_acct.rec[type].total_allocs, 2); +    assert_int_equal(xl->mem_acct.rec[type].max_size, size+1024); +    assert_int_equal(xl->mem_acct.rec[type].max_num_allocs, 2); + +    // Check memory +    helper_check_memory_headers(mem - sizeof(mem_header_t), xl, size, type); +    free(mem - sizeof(mem_header_t)); +    helper_xlator_destroy(xl); +} + +static void +test_gf_realloc_ptr(void **state) +{ +    xlator_t *xl; +    void *mem; +    size_t size; + +    // Initialize xl +    xl = helper_xlator_init(10); +    assert_int_equal(xl->ctx->mem_acct_enable, 0); + +    // For line mem-pool.c:115 and mem-pool:118 +    will_always_return(__glusterfs_this_location, &xl); + +    // Tests according to the manpage for realloc + +    // Like a malloc +    size = 1024; +    mem = __gf_realloc(NULL, size); +    assert_non_null(mem); +    memset(mem, 0xA5, size); + +    // Like a free +    mem = __gf_realloc(mem, 0); +    assert_null(mem); + +    // Now enable xl context +    xl->ctx->mem_acct_enable = 1; +    expect_assert_failure(__gf_realloc(NULL, size)); + +    helper_xlator_destroy(xl); +} + +int main(void) { +    const UnitTest tests[] = { +        unit_test(test_gf_mem_acct_enable_set), +        unit_test(test_gf_mem_set_acct_info_asserts), +        unit_test(test_gf_mem_set_acct_info_memory), +        unit_test(test_gf_calloc_default_calloc), +        unit_test(test_gf_calloc_mem_acct_enabled), +        unit_test(test_gf_malloc_default_malloc), +        unit_test(test_gf_malloc_mem_acct_enabled), +        unit_test(test_gf_realloc_default_realloc), +        unit_test(test_gf_realloc_mem_acct_enabled), +        unit_test(test_gf_realloc_ptr), +    }; + +    return run_tests(tests, "libglusterfs_mem_pool"); +} diff --git a/xlators/cluster/dht/src/Makefile.am b/xlators/cluster/dht/src/Makefile.am index 174bea84110..3032705b5d5 100644 --- a/xlators/cluster/dht/src/Makefile.am +++ b/xlators/cluster/dht/src/Makefile.am @@ -36,3 +36,18 @@ uninstall-local:  install-data-hook:  	ln -sf dht.so $(DESTDIR)$(xlatordir)/distribute.so + +#### UNIT TESTS ##### +CLEANFILES += *.gcda *.gcno *_xunit.xml +noinst_PROGRAMS = +TESTS = + +dht_layout_unittest_CPPFLAGS = $(UNITTEST_CPPFLAGS) $(AM_CPPFLAGS) +dht_layout_unittest_SOURCES = unittest/dht_layout_unittest.c \ +                              unittest/dht_layout_mock.c \ +                              dht-layout.c +dht_layout_unittest_CFLAGS = $(UNITTEST_CFLAGS) +dht_layout_unittest_LDADD = $(UNITTEST_LDADD) +dht_layout_unittest_LDFLAGS = $(UNITTEST_LDFLAGS) +noinst_PROGRAMS += dht_layout_unittest +TESTS += dht_layout_unittest diff --git a/xlators/cluster/dht/src/dht-layout.c b/xlators/cluster/dht/src/dht-layout.c index 31d85a5060b..deaa493f908 100644 --- a/xlators/cluster/dht/src/dht-layout.c +++ b/xlators/cluster/dht/src/dht-layout.c @@ -25,6 +25,19 @@  #define layout_size(cnt) (layout_base_size + (cnt * layout_entry_size)) +#include <cmockery/pbc.h> +#include <cmockery/cmockery_override.h> + +// Change GF_CALLOC and GF_FREE to use +// cmockery2 memory allocation versions +#ifdef UNIT_TESTING +#undef GF_CALLOC +#define GF_CALLOC(n, s, t) test_calloc(n, s) +#undef GF_FREE +#define GF_FREE test_free +#endif + +  dht_layout_t *  dht_layout_new (xlator_t *this, int cnt) @@ -32,6 +45,8 @@ dht_layout_new (xlator_t *this, int cnt)          dht_layout_t *layout = NULL;          dht_conf_t   *conf = NULL; +        REQUIRE(NULL != this); +        REQUIRE(cnt >= 0);          conf = this->private; @@ -50,6 +65,11 @@ dht_layout_new (xlator_t *this, int cnt)          }          layout->ref = 1; + +        ENSURE(NULL != layout); +        ENSURE(layout->type == DHT_HASH_TYPE_DM); +        ENSURE(layout->cnt == cnt); +        ENSURE(layout->ref == 1);  out:          return layout;  } diff --git a/xlators/cluster/dht/src/unittest/dht_layout_mock.c b/xlators/cluster/dht/src/unittest/dht_layout_mock.c new file mode 100644 index 00000000000..aa19ddc575d --- /dev/null +++ b/xlators/cluster/dht/src/unittest/dht_layout_mock.c @@ -0,0 +1,63 @@ +/* +  Copyright (c) 2014 Red Hat, Inc. <http://www.redhat.com> +  This file is part of GlusterFS. + +  This file is licensed to you under your choice of the GNU Lesser +  General Public License, version 3 or any later version (LGPLv3 or +  later), or the GNU General Public License, version 2 (GPLv2), in all +  cases as published by the Free Software Foundation. +*/ +#ifndef _CONFIG_H +#define _CONFIG_H +#include "config.h" +#endif + +#include "glusterfs.h" +#include "xlator.h" +#include "dht-common.h" +#include "byte-order.h" + +int +dht_hash_compute (xlator_t *this, int type, const char *name, uint32_t *hash_p) +{ +    return 0; +} + +int +dht_inode_ctx_layout_get (inode_t *inode, xlator_t *this, dht_layout_t **layout) +{ +    return 0; +} + +int +dht_inode_ctx_layout_set (inode_t *inode, xlator_t *this, +                          dht_layout_t *layout_int) +{ +    return 0; +} + +int +dict_get_ptr (dict_t *this, char *key, void **ptr) +{ +    return 0; +} + +int +dict_get_ptr_and_len (dict_t *this, char *key, void **ptr, int *len) +{ +    return 0; +} + +int _gf_log (const char *domain, const char *file, +             const char *function, int32_t line, gf_loglevel_t level, +             const char *fmt, ...) +{ +    return 0; +} + +int _gf_log_callingfn (const char *domain, const char *file, +                       const char *function, int32_t line, gf_loglevel_t level, +                       const char *fmt, ...) +{ +    return 0; +} diff --git a/xlators/cluster/dht/src/unittest/dht_layout_unittest.c b/xlators/cluster/dht/src/unittest/dht_layout_unittest.c new file mode 100644 index 00000000000..b5233d235d0 --- /dev/null +++ b/xlators/cluster/dht/src/unittest/dht_layout_unittest.c @@ -0,0 +1,124 @@ +/* +  Copyright (c) 2008-2014 Red Hat, Inc. <http://www.redhat.com> +  This file is part of GlusterFS. + +  This file is licensed to you under your choice of the GNU Lesser +  General Public License, version 3 or any later version (LGPLv3 or +  later), or the GNU General Public License, version 2 (GPLv2), in all +  cases as published by the Free Software Foundation. +*/ + +#include "dht-common.h" +#include "logging.h" +#include "xlator.h" + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <inttypes.h> +#include <cmockery/pbc.h> +#include <cmockery/cmockery.h> + +/* + * Helper functions + */ + +static xlator_t * +helper_xlator_init(uint32_t num_types) +{ +    xlator_t *xl; +    int i, ret; + +    REQUIRE(num_types > 0); + +    xl = test_calloc(1, sizeof(xlator_t)); +    assert_non_null(xl); +    xl->mem_acct.num_types = num_types; +    xl->mem_acct.rec = test_calloc(num_types, sizeof(struct mem_acct_rec)); +    assert_non_null(xl->mem_acct.rec); + +    xl->ctx = test_calloc(1, sizeof(glusterfs_ctx_t)); +    assert_non_null(xl->ctx); + +    for (i = 0; i < num_types; i++) { +            ret = LOCK_INIT(&(xl->mem_acct.rec[i].lock)); +            assert_false(ret); +    } + +    ENSURE(num_types == xl->mem_acct.num_types); +    ENSURE(NULL != xl); + +    return xl; +} + +static int +helper_xlator_destroy(xlator_t *xl) +{ +    int i, ret; + +    for (i = 0; i < xl->mem_acct.num_types; i++) { +            ret = LOCK_DESTROY(&(xl->mem_acct.rec[i].lock)); +            assert_int_equal(ret, 0); +    } + +    free(xl->mem_acct.rec); +    free(xl->ctx); +    free(xl); +    return 0; +} + +/* + * Unit tests + */ +static void +test_dht_layout_new(void **state) +{ +    xlator_t *xl; +    dht_layout_t *layout; +    dht_conf_t   *conf; +    int cnt; + +    expect_assert_failure(dht_layout_new(NULL, 0)); +    expect_assert_failure(dht_layout_new((xlator_t *)0x12345, -1)); +    xl = helper_xlator_init(10); + +    // xl->private is NULL +    assert_null(xl->private); +    cnt = 100; +    layout = dht_layout_new(xl, cnt); +    assert_non_null(layout); +    assert_int_equal(layout->type, DHT_HASH_TYPE_DM); +    assert_int_equal(layout->cnt, cnt); +    assert_int_equal(layout->ref, 1); +    assert_int_equal(layout->gen, 0); +    assert_int_equal(layout->spread_cnt, 0); +    free(layout); + +    // xl->private is not NULL +    cnt = 110; +    conf = (dht_conf_t *)test_calloc(1, sizeof(dht_conf_t)); +    assert_non_null(conf); +    conf->dir_spread_cnt = 12345; +    conf->gen = -123; +    xl->private = conf; + +    layout = dht_layout_new(xl, cnt); +    assert_non_null(layout); +    assert_int_equal(layout->type, DHT_HASH_TYPE_DM); +    assert_int_equal(layout->cnt, cnt); +    assert_int_equal(layout->ref, 1); +    assert_int_equal(layout->gen, conf->gen); +    assert_int_equal(layout->spread_cnt, conf->dir_spread_cnt); +    free(layout); + +    free(conf); +    helper_xlator_destroy(xl); +} + +int main(void) { +    const UnitTest tests[] = { +        unit_test(test_dht_layout_new), +    }; + +    return run_tests(tests, "xlator_dht_layout"); +} diff --git a/xlators/storage/posix/src/posix-helpers.c b/xlators/storage/posix/src/posix-helpers.c index 2cf46669e57..5725cad7d92 100644 --- a/xlators/storage/posix/src/posix-helpers.c +++ b/xlators/storage/posix/src/posix-helpers.c @@ -266,6 +266,16 @@ _posix_xattr_get_set (dict_t *xattr_req,                                  goto err;                          } +                        /* +                         * There could be a situation where the ia_size is +                         * zero. GF_CALLOC will return a pointer to the +                         * memory initialized by gf_mem_set_acct_info. +                         * This function adds a header and a footer to +                         * the allocated memory.  The returned pointer +                         * points to the memory just after the header, but +                         * when size is zero, there is no space for user +                         * data. The memory can be freed by calling GF_FREE. +                         */                          databuf = GF_CALLOC (1, filler->stbuf->ia_size,                                               gf_posix_mt_char);                          if (!databuf) {  | 
