diff --git a/ompi/mca/btl/usnic/Makefile.am b/ompi/mca/btl/usnic/Makefile.am index 8906bd7217..dc847d76ec 100644 --- a/ompi/mca/btl/usnic/Makefile.am +++ b/ompi/mca/btl/usnic/Makefile.am @@ -11,7 +11,7 @@ # All rights reserved. # Copyright (c) 2006 Sandia National Laboratories. All rights # reserved. -# Copyright (c) 2010-2013 Cisco Systems, Inc. All rights reserved. +# Copyright (c) 2010-2014 Cisco Systems, Inc. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -22,6 +22,8 @@ AM_CPPFLAGS = $(btl_usnic_CPPFLAGS) AM_CFLAGS = $(btl_usnic_CFLAGS) +EXTRA_DIST = README.txt README.test + dist_ompidata_DATA = \ help-mpi-btl-usnic.txt @@ -48,7 +50,9 @@ sources = \ btl_usnic_stats.h \ btl_usnic_stats.c \ btl_usnic_util.c \ - btl_usnic_util.h + btl_usnic_util.h \ + btl_usnic_test.c \ + btl_usnic_test.h if OPAL_HAVE_HWLOC sources += btl_usnic_hwloc.c @@ -81,3 +85,9 @@ noinst_LTLIBRARIES = $(lib) libmca_btl_usnic_la_SOURCES = $(lib_sources) libmca_btl_usnic_la_LDFLAGS= -module -avoid-version $(btl_usnic_LDFLAGS) libmca_btl_usnic_la_LIBADD = $(btl_usnic_LIBS) + +if OMPI_BTL_USNIC_BUILD_UNIT_TESTS +ompi_btl_usnic_run_tests_SOURCES = test/ompi_btl_usnic_run_tests.c +ompi_btl_usnic_run_tests_LDADD = -ldl +bin_PROGRAMS = ompi_btl_usnic_run_tests +endif OMPI_BTL_USNIC_BUILD_UNIT_TESTS diff --git a/ompi/mca/btl/usnic/README.test b/ompi/mca/btl/usnic/README.test new file mode 100644 index 0000000000..18aa4ed6ea --- /dev/null +++ b/ompi/mca/btl/usnic/README.test @@ -0,0 +1,74 @@ +usnic BTL Unit Testing Information +================================== +This document briefly describes the scheme put in place for usnic BTL unit +testing. This system was very quickly tossed together and warrants a proper +revisiting at some point in the future. There are lots of ways to solve these +problems and this is just one of them. Future improvement is welcome. + +Goals +----- +* To enable _unit_ testing of isolated functions or sets of functions. This + has all sorts of benefits, including: + - greater confidence that corner cases work as expected + - faster, lower-stress refactoring in the future + - influencing future interfaces to have less implicit coupling/state (unit + testing is harder otherwise) +* To be able to test *static* functions as well as non-static ones. +* To avoid cluttering the normal code base with excessive test-related + macros/code. +* The tests should be easy to run under Valgrind and GDB to facilitate: + - automated leak/memory checking + - easy debugging compared to a parallel MPI environment + +Anti-Goals +---------- +* Testing the low level networking API (e.g., verbs). +* Testing inter-process interaction, such as ORTE-related functionality. + +Constraints +----------- +* our unit tests should never perturb a normal build in terms of performance + or correctness + - also should not affect other non-usNIC developers in any way (don't + break Ralph's `make check`) +* static functions are difficult to test from outside the same source file + +Design Notes +------------ +* Source files named `X.c` include a header at the end named `test/X_test.h` + - Rationale: gives tests access to the static functions in `X.c` + - Rationale: keeps `X.c` clutter-free +* unit test infrastructure lives in `btl_usnic_test.c` and `btl_usnic_test.h` +* unit test functionality is built and enabled by passing + `--enable-ompi-btl-usnic-unit-tests` to configure + - Rationale: default state disables all unit test logic, achieving our + "non-interference" goals +* The tests are run by a new executable that gets built when unit tests are + enabled: `ompi_btl_usnic_run_tests`. +* Tests are registered at dlopen time via an + `__attribute__((__constructor__))` function that is generated by invocations + of the `USNIC_REGISTER_TEST` macro. + - Rationale: add tests in one spot, no need to centralize the list of + tests to run separately from the tests themselves. +* Tests only use a simple `check()` macro right now that has `assert`-like + semantics. + - this could easily be expanded in the future, using the check docs as + inspiration: + http://check.sourceforge.net/doc/check_html/check_4.html#Convenience-Test-Functions + +Worthy Future Goals/Features +---------------------------- +* Add some mocking capabilities. + - could use the preprocessor to replace regular function/macro calls with + calls to functions/macros that dispatch to changeable function pointers + - will require some reorganization of some of the existing code... +* Output test results in a format that Jenkins and other tools can understand. + - TAP + - jUnit XML +* Possibly utilize part or all of an existing unit testing framework, e.g.: + - check: http://check.sourceforge.net/ (LGPLed) + - cUnit: http://cunit.sourceforge.net/ (unfortunately GPLed...) +* Re-examine test grouping and numbering. Right now there's no real concept + of "suites" or multiple cases within a single test function. Once the + number of tests grows to a certain point it will probably make sense to + revisit this decision. diff --git a/ompi/mca/btl/usnic/btl_usnic_component.c b/ompi/mca/btl/usnic/btl_usnic_component.c index 4893969e0c..b55666f1f4 100644 --- a/ompi/mca/btl/usnic/btl_usnic_component.c +++ b/ompi/mca/btl/usnic/btl_usnic_component.c @@ -73,6 +73,7 @@ #include "btl_usnic_send.h" #include "btl_usnic_recv.h" #include "btl_usnic_proc.h" +#include "btl_usnic_test.h" #define OMPI_BTL_USNIC_NUM_WC 500 #define max(a,b) ((a) > (b) ? (a) : (b)) @@ -213,6 +214,11 @@ static int usnic_component_close(void) mca_btl_usnic_component.vendor_part_ids = NULL; } +#if OMPI_BTL_USNIC_UNIT_TESTS + /* clean up the unit test infrastructure */ + ompi_btl_usnic_cleanup_tests(); +#endif + return OMPI_SUCCESS; } diff --git a/ompi/mca/btl/usnic/btl_usnic_test.c b/ompi/mca/btl/usnic/btl_usnic_test.c new file mode 100644 index 0000000000..a063199cb6 --- /dev/null +++ b/ompi/mca/btl/usnic/btl_usnic_test.c @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. All rights reserved. + * $COPYRIGHT$ + * + * Additional copyrights may follow + * + * $HEADER$ + */ + +#include + +#include "opal/class/opal_list.h" + +#include "btl_usnic.h" +#include "btl_usnic_test.h" + +int ompi_btl_usnic_num_tests_run = 0; +int ompi_btl_usnic_num_tests_passed = 0; +int ompi_btl_usnic_num_tests_failed = 0; +int ompi_btl_usnic_num_tests_skipped = 0; + +struct test_info { + opal_list_item_t li; + char *name; + ompi_btl_usnic_test_fn_t test_fn; + void *ctx; +}; + +#if OMPI_BTL_USNIC_UNIT_TESTS +static bool initialized = false; +static opal_list_t all_tests; + +void ompi_btl_usnic_cleanup_tests(void) +{ + opal_list_item_t *li; + struct test_info *info; + + if (initialized) { + while (NULL != (li = opal_list_remove_first(&all_tests))) { + info = container_of(li, struct test_info, li); + free(info); + } + OBJ_DESTRUCT(&all_tests); + } + initialized = false; +} + +static void init_test_infra(void) +{ + if (!initialized) { + OBJ_CONSTRUCT(&all_tests, opal_list_t); + initialized = true; + } +} + +void ompi_btl_usnic_register_test(const char *name, + ompi_btl_usnic_test_fn_t test_fn, + void *ctx) +{ + struct test_info *info = malloc(sizeof(*info)); + assert(info != NULL); + + OBJ_CONSTRUCT(&info->li, opal_list_item_t); + + init_test_infra(); + + info->name = strdup(name); + info->test_fn = test_fn; + info->ctx = ctx; + + opal_list_append(&all_tests, &info->li); +} + +void ompi_btl_usnic_run_tests(void) +{ + struct test_info *info; + enum test_result result; + + if (!OMPI_BTL_USNIC_UNIT_TESTS) { + test_out("unit tests disabled in this build, doing nothing!\n"); + return; + } + test_out("STARTING TESTS\n"); + + OPAL_LIST_FOREACH(info, &all_tests, struct test_info) { + test_out("running test '%s'... ", info->name); + result = info->test_fn(info->ctx); + + ++ompi_btl_usnic_num_tests_run; + switch (result) { + case TEST_PASSED: + ++ompi_btl_usnic_num_tests_passed; + test_out("PASSED\n"); + break; + case TEST_FAILED: + ++ompi_btl_usnic_num_tests_failed; + test_out("FAILED\n"); + break; + case TEST_SKIPPED: + ++ompi_btl_usnic_num_tests_skipped; + test_out("SKIPPED\n"); + break; + } + } + + test_out("FINISHED TESTS (%d passed, %d failed, %d skipped)\n", + ompi_btl_usnic_num_tests_passed, + ompi_btl_usnic_num_tests_failed, + ompi_btl_usnic_num_tests_skipped); +} + +#else /* !OMPI_BTL_USNIC_UNIT_TESTS */ + +void ompi_btl_usnic_register_test(const char *name, + ompi_btl_usnic_test_fn_t test_fn, + void *ctx) +{ + abort(); /* never should be called */ +} + +void ompi_btl_usnic_run_tests(void) +{ + test_out("unit tests disabled in this build, doing nothing!\n"); + return; +} + +#endif /* !OMPI_BTL_USNIC_UNIT_TESTS */ diff --git a/ompi/mca/btl/usnic/btl_usnic_test.h b/ompi/mca/btl/usnic/btl_usnic_test.h new file mode 100644 index 0000000000..3216a06252 --- /dev/null +++ b/ompi/mca/btl/usnic/btl_usnic_test.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. All rights reserved. + * $COPYRIGHT$ + * + * Additional copyrights may follow + * + * $HEADER$ + */ + +#ifndef BTL_USNIC_TEST_H +#define BTL_USNIC_TEST_H + +#include "ompi_config.h" + +typedef int (*ompi_btl_usnic_test_fn_t)(void *ctx); + +#if OMPI_BTL_USNIC_UNIT_TESTS +# define test_out(...) fprintf(stderr, __VA_ARGS__) +# define check(a) \ + do { \ + if (!(a)) { \ + test_out("%s:%d: check failed, '%s'\n", __func__, __LINE__, #a); \ + return TEST_FAILED; \ + } \ + } while (0) +# define check_str_eq(a,b) \ + do { \ + const char *a_ = (a); \ + const char *b_ = (b); \ + if (0 != strcmp(a_,b_)) { \ + test_out("%s:%d: check failed, \"%s\" != \"%s\"\n", \ + __func__, __LINE__, a_, b_); \ + return TEST_FAILED; \ + } \ + } while (0) +# define check_int_eq(got, expected) \ + do { \ + if ((got) != (expected)) { \ + test_out("%s:%d: check failed, \"%s\" != \"%s\", got %d\n", \ + __func__, __LINE__, #got, #expected, (got)); \ + return TEST_FAILED; \ + } \ + } while (0) +/* just use check_int_eq for now, no public error code to string routine + * exists (opal_err2str is static) */ +# define check_err_code(got, expected) \ + check_int_eq(got, expected) +# define check_msg(a, msg) \ + do { \ + if (!(a)) { \ + test_out("%s:%d: check failed, \"%s\" (%s)\n", \ + __func__, __LINE__, #a, (msg)); \ + return TEST_FAILED; \ + } \ + } while (0) + +extern int ompi_btl_usnic_num_tests_run; +extern int ompi_btl_usnic_num_tests_passed; +extern int ompi_btl_usnic_num_tests_failed; +extern int ompi_btl_usnic_num_tests_skipped; + +enum test_result { + TEST_PASSED = 0, + TEST_FAILED, + TEST_SKIPPED +}; + +/* let us actually paste __LINE__ with other tokens */ +# define USNIC_PASTE(a,b) USNIC_PASTE2(a,b) +# define USNIC_PASTE2(a,b) a ## b +/* A helper macro to de-clutter test registration. */ +# define USNIC_REGISTER_TEST(name, test_fn, ctx) \ +__attribute__((__constructor__)) \ +static void USNIC_PASTE(usnic_reg_ctor_,__LINE__)(void) \ +{ \ + ompi_btl_usnic_register_test(name, test_fn, ctx); \ +} \ + +#else /* !OMPI_BTL_USNIC_UNIT_TESTS */ +# define test_out(...) do {} while(0) +# define USNIC_REGISTER_TEST(name, test_fn, ctx) +#endif + +/* Run all registered tests. Typically called by an external utility that + * dlopens the usnic BTL shared object. See run_usnic_tests.c. */ +void ompi_btl_usnic_run_tests(void); + +void ompi_btl_usnic_register_test(const char *name, + ompi_btl_usnic_test_fn_t test_fn, + void *ctx); + +/* should be called once, at component close time */ +void ompi_btl_usnic_cleanup_tests(void); + +#endif /* BTL_USNIC_TEST_H */ diff --git a/ompi/mca/btl/usnic/configure.m4 b/ompi/mca/btl/usnic/configure.m4 index d5640491e4..85f4c5fcd5 100644 --- a/ompi/mca/btl/usnic/configure.m4 +++ b/ompi/mca/btl/usnic/configure.m4 @@ -20,6 +20,88 @@ # $HEADER$ # +# OMPI_CHECK_LIBNL3(prefix, [action-if-found], [action-if-not-found]) +# -------------------------------------------------------- +# check if libnl3 support can be found. sets prefix_{CPPFLAGS, +# LDFLAGS, LIBS} as needed and runs action-if-found if there is +# support, otherwise executes action-if-not-found +# +# libnl3 changed its default header location as of v3.2 (released ca. September +# 2011). It was previously "${prefix}/include/netlink/...". It now is +# "${prefix}/libnl3/include/netlink/...". The logic below only supports +# >=v3.2, under the assumption that it is not widely deployed. +AC_DEFUN([OMPI_CHECK_LIBNL3],[ + AC_ARG_WITH([libnl3], + [AC_HELP_STRING([--with-libnl3(=DIR)], + [Build libnl3 support])]) + OMPI_CHECK_WITHDIR([libnl3], [$with_libnl3], [include/libnl3/netlink/netlink.h]) + AC_ARG_WITH([libnl3-libdir], + [AC_HELP_STRING([--with-libnl3-libdir=DIR], + [Search for libnl3 libraries in DIR])]) + OMPI_CHECK_WITHDIR([libnl3-libdir], [$with_libnl3_libdir], [libnl-3.*]) + + ompi_check_libnl3_$1_save_CPPFLAGS="$CPPFLAGS" + ompi_check_libnl3_$1_save_LDFLAGS="$LDFLAGS" + ompi_check_libnl3_$1_save_LIBS="$LIBS" + + ompi_check_libnl3_$1_orig_CPPFLAGS="$$1_CPPFLAGS" + ompi_check_libnl3_$1_orig_LDFLAGS="$$1_LDFLAGS" + ompi_check_libnl3_$1_orig_LIBS="$$1_LIBS" + + AS_IF([test "$with_libnl3" != "no"], + [AS_IF([test ! -z "$with_libnl3" -a "$with_libnl3" != "yes"], + [ompi_check_libnl3_dir="$with_libnl3"]) + AS_IF([test ! -z "$with_libnl3_libdir" -a "$with_libnl3_libdir" != "yes"], + [ompi_check_libnl3_libdir="$with_libnl3_libdir"]) + + # OMPI_CHECK_PACKAGE unfortunately can't handle this weird include + # dir layout + AS_IF([test -n "$ompi_check_libnl3_dir"], + [ompi_check_libnl3_includedir="$ompi_check_libnl3_dir/include/libnl3"], + [ompi_check_libnl3_includedir="/usr/include/libnl3"]) + $1_CPPFLAGS="$$1_CPPFLAGS -I$ompi_check_libnl3_includedir" + CPPFLAGS="$CPPFLAGS -I$ompi_check_libnl3_includedir" + + AC_CHECK_HEADER([netlink/netlink.h], + [# nl_recvmsgs_report appears to be a symbol which + # is present in libnl-3 but not libnl (v1) + _OMPI_CHECK_PACKAGE_LIB([$1], + [nl-3], + [nl_recvmsgs_report], + [], + [$ompi_check_libnl3_dir], + [$ompi_check_libnl3_libdir], + [ompi_check_libnl3_happy="yes"], + [ompi_check_libnl3_happy="no"])], + [ompi_check_libnl3_happy=no]) + + # make sure that we don't pollute the cache with the results of a + # test performed under different CPPFLAGS + AS_UNSET([ac_cv_header_netlink_netlink_h])], + [ompi_check_libnl3_happy="no"]) + + # restore global flags + CPPFLAGS="$ompi_check_libnl3_$1_save_CPPFLAGS" + LDFLAGS="$ompi_check_libnl3_$1_save_LDFLAGS" + LIBS="$ompi_check_libnl3_$1_save_LIBS" + + AS_IF([test "$ompi_check_libnl3_happy" = "yes"], + [$2], + [AS_IF([test ! -z "$with_libnl3" -a "$with_libnl3" != "no"], + [AC_MSG_ERROR([libnl3 support requested but not found. Aborting])]) + # restore prefixed flags on failure + $1_CPPFLAGS="$ompi_check_package_$1_orig_CPPFLAGS" + $1_LDFLAGS="$ompi_check_package_$1_orig_LDFLAGS" + $1_LIBS="$ompi_check_package_$1_orig_LIBS" + $3]) +]) + +# MCA_ompi_btl_usnic_POST_CONFIG([should_build]) +# ------------------------------------------ +AC_DEFUN([MCA_ompi_btl_usnic_POST_CONFIG], [ + AM_CONDITIONAL([OMPI_BTL_USNIC_BUILD_UNIT_TESTS], + [test "$1" -eq 1 && test "X$enable_ompi_btl_usnic_unit_tests" = "Xyes"]) +]) # MCA_btl_usnic_CONFIG([action-if-can-compile], # [action-if-cant-compile]) @@ -27,6 +109,20 @@ AC_DEFUN([MCA_ompi_btl_usnic_CONFIG],[ AC_CONFIG_FILES([ompi/mca/btl/usnic/Makefile]) + # see README.test for information about this scheme + AC_ARG_ENABLE([ompi-btl-usnic-unit-tests], + [AS_HELP_STRING([--enable-ompi-btl-usnic-unit-tests], + [build unit tests for the usnic BTL, + including the test runner program, + ompi_btl_usnic_run_tests])]) + AS_IF([test "X$enable_ompi_btl_usnic_unit_tests" = "Xyes"], + [unit_tests=1 + AC_MSG_NOTICE([enabling usnic BTL unit tests])], + [unit_tests=0]) + AC_DEFINE_UNQUOTED([OMPI_BTL_USNIC_UNIT_TESTS], [$unit_tests], + [define to 1 if usnic BTL unit tests are enabled, 0 otherwise]) + unset unit_tests + OMPI_CHECK_OPENFABRICS([btl_usnic], [btl_usnic_happy="yes"], [btl_usnic_happy="no"]) diff --git a/ompi/mca/btl/usnic/test/ompi_btl_usnic_run_tests.c b/ompi/mca/btl/usnic/test/ompi_btl_usnic_run_tests.c new file mode 100644 index 0000000000..0ecce4513e --- /dev/null +++ b/ompi/mca/btl/usnic/test/ompi_btl_usnic_run_tests.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. All rights reserved. + * $COPYRIGHT$ + * + * Additional copyrights may follow + * + * $HEADER$ + */ + +/* A simple test runner program for the usnic BTL unit tests. See README.test + * for more information. */ + +/* for dladdr() */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include /* for dirname() */ +#include + +#include + +#define MCA_BTL_USNIC_SO "mca_btl_usnic.so" + +typedef void (*run_tests_fn_t)(void); + +int main(int argc, char **argv) +{ + void *mpi_handle; + void *usnic_handle; + void (*run_tests)(void); + int (*init)(int *, char ***); + int (*finalize)(void); + Dl_info info; + char *libmpi_path; + char *path; + char *to; + int path_len; + + mpi_handle = dlopen("libmpi.so", RTLD_NOW|RTLD_GLOBAL); + if (mpi_handle == NULL) { + fprintf(stderr, "mpi_handle=NULL dlerror()=%s\n", dlerror()); + abort(); + } + + /* casting awfulness needed for GCC's "-pedantic" option :( */ + *(void **)(&init) = dlsym(mpi_handle, "MPI_Init"); + if (init == NULL) { + fprintf(stderr, "init=NULL dlerror()=%s\n", dlerror()); + abort(); + } + /* casting awfulness needed for GCC's "-pedantic" option :( */ + *(void **)(&finalize) = dlsym(mpi_handle, "MPI_Finalize"); + if (finalize == NULL) { + fprintf(stderr, "finalize=%p dlerror()=%s\n", *(void **)(&finalize), dlerror()); + abort(); + } + + /* call MPI_Init this way to avoid build-time dependency issues */ + init(&argc, &argv); + + /* figure out where the usnic BTL shared object is relative to libmpi.so */ + if (!dladdr(*(void **)(&init), &info)) { + fprintf(stderr, "ERROR: unable to dladdr(init,...)\n"); + abort(); + } + libmpi_path = strdup(info.dli_fname); + assert(libmpi_path != NULL); + path_len = strlen(libmpi_path) + strlen("/openmpi/") + strlen(MCA_BTL_USNIC_SO); + path = calloc(path_len+1, 1); + to = path; + to = stpcpy(to, dirname(libmpi_path)); + to = stpcpy(to, "/openmpi/"); + to = stpcpy(to, MCA_BTL_USNIC_SO); + + usnic_handle = dlopen(path, RTLD_NOW|RTLD_LOCAL); + if (usnic_handle == NULL) { + fprintf(stderr, "usnic_handle=%p dlerror()=%s\n", (void *)usnic_handle, dlerror()); + abort(); + } + + free(libmpi_path); + free(path); + + /* casting awfulness needed for GCC's "-pedantic" option :( */ + *(void **)(&run_tests) = dlsym(usnic_handle, "ompi_btl_usnic_run_tests"); + if (run_tests == NULL) { + fprintf(stderr, "run_tests=%p dlerror()=%s\n", *(void **)(&run_tests), dlerror()); + abort(); + } + run_tests(); + + finalize(); + + /* deliberately do not dlclose() either handle so that any valgrind stack + * traces are more useful */ + + return 0; +}