From: Jens Neuhalfen <j...@neuhalfen.name> cmocka [1,2] is a testing framework for C. Adding unit test capabilities to the openvpn repository will greatly ease the task of writing correct code.
cmocka source code is added as git submodule in ./vendor. A submodule approach has been chosen over a classical library dependency because libcmocka is not available, or only available in very old versions (e.g. on Ubuntu). cmocka is build during 'make check' and installed in vendor/dist/. [1] https://cmocka.org/ [2] https://lwn.net/Articles/558106/ Signed-off-by: Jens Neuhalfen <j...@neuhalfen.name> --- .gitmodules | 4 +++ Makefile.am | 2 +- configure.ac | 16 +++++++++++ tests/Makefile.am | 3 +- tests/unit_tests/.gitignore | 1 + tests/unit_tests/Makefile.am | 3 ++ tests/unit_tests/README.md | 40 ++++++++++++++++++++++++++ tests/unit_tests/example_test/Makefile.am | 13 +++++++++ tests/unit_tests/example_test/README.md | 3 ++ tests/unit_tests/example_test/test.c | 47 +++++++++++++++++++++++++++++++ tests/unit_tests/example_test/test2.c | 22 +++++++++++++++ vendor/.gitignore | 2 ++ vendor/Makefile.am | 24 ++++++++++++++++ vendor/README.md | 9 ++++++ vendor/cmocka | 1 + 15 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 100644 tests/unit_tests/.gitignore create mode 100644 tests/unit_tests/Makefile.am create mode 100644 tests/unit_tests/README.md create mode 100644 tests/unit_tests/example_test/Makefile.am create mode 100644 tests/unit_tests/example_test/README.md create mode 100644 tests/unit_tests/example_test/test.c create mode 100644 tests/unit_tests/example_test/test2.c create mode 100644 vendor/.gitignore create mode 100644 vendor/Makefile.am create mode 100644 vendor/README.md create mode 160000 vendor/cmocka diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e9c6388 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "vendor/cmocka"] + path = vendor/cmocka + url = git://git.cryptomilk.org/projects/cmocka.git + branch = master diff --git a/Makefile.am b/Makefile.am index 66d9f23..364785c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -54,7 +54,7 @@ BUILT_SOURCES = \ config-version.h endif -SUBDIRS = build distro include src sample doc tests +SUBDIRS = build distro include src sample doc vendor tests dist_doc_DATA = \ README \ diff --git a/configure.ac b/configure.ac index 97ad856..fb3fa3c 100644 --- a/configure.ac +++ b/configure.ac @@ -1198,6 +1198,19 @@ sampledir="\$(docdir)/sample" AC_SUBST([plugindir]) AC_SUBST([sampledir]) +VENDOR_SRC_ROOT="\$(abs_top_srcdir)/vendor/" +VENDOR_DIST_ROOT="\$(abs_top_builddir)/vendor/dist" +VENDOR_BUILD_ROOT="\$(abs_top_builddir)/vendor/.build" +AC_SUBST([VENDOR_SRC_ROOT]) +AC_SUBST([VENDOR_BUILD_ROOT]) +AC_SUBST([VENDOR_DIST_ROOT]) + +TEST_LDFLAGS="-lcmocka -L\$(abs_top_builddir)/vendor/dist/lib -Wl,-rpath,\$(abs_top_builddir)/vendor/dist/lib" +TEST_CFLAGS="-I\$(top_srcdir)/include -I\$(abs_top_builddir)/vendor/dist/include" + +AC_SUBST([TEST_LDFLAGS]) +AC_SUBST([TEST_CFLAGS]) + AC_CONFIG_FILES([ version.sh Makefile @@ -1216,6 +1229,9 @@ AC_CONFIG_FILES([ src/plugins/auth-pam/Makefile src/plugins/down-root/Makefile tests/Makefile + tests/unit_tests/Makefile + tests/unit_tests/example_test/Makefile + vendor/Makefile sample/Makefile doc/Makefile ]) diff --git a/tests/Makefile.am b/tests/Makefile.am index b7980e0..2cba9e6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -12,6 +12,8 @@ MAINTAINERCLEANFILES = \ $(srcdir)/Makefile.in +SUBDIRS = unit_tests + test_scripts = t_client.sh t_lpback.sh t_cltsrv.sh TESTS_ENVIRONMENT = top_srcdir="$(top_srcdir)" @@ -20,4 +22,3 @@ TESTS = $(test_scripts) dist_noinst_SCRIPTS = \ $(test_scripts) \ t_cltsrv-down.sh - diff --git a/tests/unit_tests/.gitignore b/tests/unit_tests/.gitignore new file mode 100644 index 0000000..8655de8 --- /dev/null +++ b/tests/unit_tests/.gitignore @@ -0,0 +1 @@ +*_testdriver diff --git a/tests/unit_tests/Makefile.am b/tests/unit_tests/Makefile.am new file mode 100644 index 0000000..18267bd --- /dev/null +++ b/tests/unit_tests/Makefile.am @@ -0,0 +1,3 @@ +AUTOMAKE_OPTIONS = foreign + +SUBDIRS = example_test diff --git a/tests/unit_tests/README.md b/tests/unit_tests/README.md new file mode 100644 index 0000000..ef81b23 --- /dev/null +++ b/tests/unit_tests/README.md @@ -0,0 +1,40 @@ +Unit Tests +=========== + +This directory contains unit tests for openvpn. New features/bugfixes should be written in a test friendly way and come with corresponding tests. + +Run tests +---------- + +Tests are run by `make check`. A failed tests stops test execution. To run all +tests regardless of errors call `make -k check`. + +Add new tests to existing test suite +------------------------------------- + +Test suites are organized in directories. [example_test/](example_test/) is an example +for a test suite with two test executables. Feel free to use it as a template for new tests. + +Test suites +-------------------- + +Test suites live inside a subdirectory of `$ROOT/tests/unit_tests`, e.g. `$ROOT/tests/unit_tests/my_feature`. + +Test suites are configured by a `Makefile.am`. Tests are executed by testdrivers. One testsuite can contain more than one testdriver. + +### Hints +* Name suites & testdrivers in a way that the name of the driver says something about which component/feature is tested +* Name the testdriver executable `*_testdriver`. This way it gets picked up by the default `.gitignore` + * If this is not feasible: Add all output to a `.gitignore`* Use descriptive test names: `coffee_brewing__with_no_beans__fails` vs. `test34` +* Testing a configurable feature? Wrap test execution with a conditional (see [auth_pam](plugins/auth-pam/Makefile.am) for an example) +* Add multiple test-drivers when one testdriver looks crowded with tests + +### New Test Suites +1. Organize tests in folders for features. +2. Add the new test directory to `SUBDIRS` in `Makefile.am` +3. Edit `configure.ac` and add the new `Makefile` to `AC_CONFIG_FILES` +4. Run `./configure`, and *enable* the feature you'd like to test +5. Make sure that `make check` runs your tests +6. Check: Would a stranger be able to easily find your tests by you looking at the test output? +7. Run `./configure`, and *disable* the feature you'd like to test +8. Make sure that `make check` does *not run* your tests diff --git a/tests/unit_tests/example_test/Makefile.am b/tests/unit_tests/example_test/Makefile.am new file mode 100644 index 0000000..04a5ad3 --- /dev/null +++ b/tests/unit_tests/example_test/Makefile.am @@ -0,0 +1,13 @@ +AUTOMAKE_OPTIONS = foreign + +check_PROGRAMS = example_testdriver example2_testdriver + +TESTS = $(check_PROGRAMS) + +example_testdriver_CFLAGS = @TEST_CFLAGS@ +example_testdriver_LDFLAGS = @TEST_LDFLAGS@ +example_testdriver_SOURCES = test.c + +example2_testdriver_CFLAGS = @TEST_CFLAGS@ +example2_testdriver_LDFLAGS = @TEST_LDFLAGS@ +example2_testdriver_SOURCES = test2.c diff --git a/tests/unit_tests/example_test/README.md b/tests/unit_tests/example_test/README.md new file mode 100644 index 0000000..cb75d68 --- /dev/null +++ b/tests/unit_tests/example_test/README.md @@ -0,0 +1,3 @@ +This test only checks that test compilation works. This example contains two test executables. + +These tests can be used as template for 'real' tests. diff --git a/tests/unit_tests/example_test/test.c b/tests/unit_tests/example_test/test.c new file mode 100644 index 0000000..c746b8b --- /dev/null +++ b/tests/unit_tests/example_test/test.c @@ -0,0 +1,47 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <setjmp.h> +#include <cmocka.h> + +static int setup(void **state) { + int *answer = malloc(sizeof(int)); + + *answer=42; + *state=answer; + + return 0; +} + +static int teardown(void **state) { + free(*state); + + return 0; +} + +static void null_test_success(void **state) { + (void) state; +} + +static void int_test_success(void **state) { + int *answer = *state; + assert_int_equal(*answer, 42); +} + +static void failing_test(void **state) { + // This tests fails to test that make check fails + assert_int_equal(0, 42); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(null_test_success), + cmocka_unit_test_setup_teardown(int_test_success, setup, teardown), +// cmocka_unit_test(failing_test), + }; + + return cmocka_run_group_tests_name("success_test", tests, NULL, NULL); +} + diff --git a/tests/unit_tests/example_test/test2.c b/tests/unit_tests/example_test/test2.c new file mode 100644 index 0000000..2924c61 --- /dev/null +++ b/tests/unit_tests/example_test/test2.c @@ -0,0 +1,22 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <setjmp.h> +#include <cmocka.h> + + +static void test_true(void **state) { + (void) state; +} + + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_true), + }; + + return cmocka_run_group_tests_name("success_test2", tests, NULL, NULL); +} + diff --git a/vendor/.gitignore b/vendor/.gitignore new file mode 100644 index 0000000..e11dfec --- /dev/null +++ b/vendor/.gitignore @@ -0,0 +1,2 @@ +.build/ +dist/ diff --git a/vendor/Makefile.am b/vendor/Makefile.am new file mode 100644 index 0000000..f68240e --- /dev/null +++ b/vendor/Makefile.am @@ -0,0 +1,24 @@ +AUTOMAKE_OPTIONS = foreign + +cmockasrc = @VENDOR_SRC_ROOT@/cmocka # needs an absolute path bc. of the cmake invocation +cmockabuild = @VENDOR_BUILD_ROOT@/cmocka +cmockainstall = @VENDOR_DIST_ROOT@ + +MAINTAINERCLEANFILES = \ + $(srcdir)/Makefile.in \ + $(cmockabuild) \ + $(cmockainstall) \ + @VENDOR_BUILD_ROOT@ + +distdir: + mkdir -p $(cmockainstall) + +libcmocka: distdir + mkdir -p $(cmockabuild) + (cd $(cmockabuild) && cmake -DCMAKE_INSTALL_PREFIX=$(cmockainstall) $(cmockasrc) && make && make install) + +check: libcmocka + +clean: + rm -rf $(cmockabuild) + rm -rf $(cmockainstall) diff --git a/vendor/README.md b/vendor/README.md new file mode 100644 index 0000000..6a46b35 --- /dev/null +++ b/vendor/README.md @@ -0,0 +1,9 @@ +Vendor +======== + +Vendor source libraries are included in this directory. Libraries are included +when there is no good way to ensure that the package is available on all +systems. + +`Makefile.am` compiles these libraries and installs them into ./dist. + diff --git a/vendor/cmocka b/vendor/cmocka new file mode 160000 index 0000000..b2732b5 --- /dev/null +++ b/vendor/cmocka @@ -0,0 +1 @@ +Subproject commit b2732b52202ae48f866a024c633466efdbb8e85a -- 2.8.3