From 0e5e2700e567b2bb0653b7b37877171e1ce3e9e8 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 16 Jun 2016 08:31:04 -0400 Subject: [PATCH 01/12] Initial test setup --- CMake/FindCMocka.cmake | 66 ++++++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 13 +++++++-- include/tests.h | 9 ++++++ 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 CMake/FindCMocka.cmake create mode 100644 include/tests.h diff --git a/CMake/FindCMocka.cmake b/CMake/FindCMocka.cmake new file mode 100644 index 000000000..76b4ba74d --- /dev/null +++ b/CMake/FindCMocka.cmake @@ -0,0 +1,66 @@ +# - Try to find CMocka +# Once done this will define +# +# CMOCKA_ROOT_DIR - Set this variable to the root installation of CMocka +# +# Read-Only variables: +# CMOCKA_FOUND - system has CMocka +# CMOCKA_INCLUDE_DIR - the CMocka include directory +# CMOCKA_LIBRARIES - Link these to use CMocka +# CMOCKA_DEFINITIONS - Compiler switches required for using CMocka +# +#============================================================================= +# Copyright (c) 2011-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_CMOCKA_ROOT_HINTS +) + +set(_CMOCKA_ROOT_PATHS + "$ENV{PROGRAMFILES}/cmocka" +) + +find_path(CMOCKA_ROOT_DIR + NAMES + include/cmocka.h + HINTS + ${_CMOCKA_ROOT_HINTS} + PATHS + ${_CMOCKA_ROOT_PATHS} +) +mark_as_advanced(CMOCKA_ROOT_DIR) + +find_path(CMOCKA_INCLUDE_DIR + NAMES + cmocka.h + PATHS + ${CMOCKA_ROOT_DIR}/include +) + +find_library(CMOCKA_LIBRARY + NAMES + cmocka + PATHS + ${CMOCKA_ROOT_DIR}/lib +) + +if (CMOCKA_LIBRARY) + set(CMOCKA_LIBRARIES + ${CMOCKA_LIBRARIES} + ${CMOCKA_LIBRARY} + ) +endif (CMOCKA_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CMocka DEFAULT_MSG CMOCKA_LIBRARIES CMOCKA_INCLUDE_DIR) + +# show the CMOCKA_INCLUDE_DIR and CMOCKA_LIBRARIES variables only in the advanced view +mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARIES) diff --git a/CMakeLists.txt b/CMakeLists.txt index 813bec4d9..5280b1d58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,18 +48,19 @@ option(enable-gdk-pixbuf "Use Pixbuf to support more image formats" YES) option(enable-binding-event "Enables binding event subscription" YES) option(zsh-completions "Zsh shell completions" NO) option(default-wallpaper "Installs the default wallpaper" YES) +option(enable-tests "Enables test suite" YES) find_package(JsonC REQUIRED) find_package(PCRE REQUIRED) find_package(WLC REQUIRED) find_package(Wayland REQUIRED) find_package(XKBCommon REQUIRED) +find_package(LibInput REQUIRED) find_package(Cairo REQUIRED) find_package(Pango REQUIRED) find_package(GdkPixbuf) find_package(PAM) - -find_package(LibInput REQUIRED) +find_package(CMocka) find_package(Backtrace) if(Backtrace_FOUND) @@ -130,6 +131,14 @@ install( COMPONENT data ) +if(enable-tests) + if (CMOCKA_FOUND) + add_subdirectory(test) + else() + message(WARNING "Not buliding tests - cmocka is required.") + endif() +endif() + if(default-wallpaper) install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/assets/ diff --git a/include/tests.h b/include/tests.h new file mode 100644 index 000000000..47b470599 --- /dev/null +++ b/include/tests.h @@ -0,0 +1,9 @@ +#ifndef __TESTS_H +#define __TESTS_H + +#include +#include +#include +#include + +#endif From f05fd9785915e37275812cf76416b48bdcdebfe0 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 16 Jun 2016 08:59:47 -0400 Subject: [PATCH 02/12] Add CMake function for configuring tests --- CMake/Test.cmake | 45 +++++++++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 1 + 2 files changed, 46 insertions(+) create mode 100644 CMake/Test.cmake diff --git a/CMake/Test.cmake b/CMake/Test.cmake new file mode 100644 index 000000000..58097529c --- /dev/null +++ b/CMake/Test.cmake @@ -0,0 +1,45 @@ +find_package(A2X REQUIRED) + +function(configure_test) + set(options) + set(oneValueArgs NAME SUBPROJECT) + set(multiValueArgs WRAPPERS SOURCES INCLUDES LIBRARIES) + cmake_parse_arguments(CONFIGURE_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + message("${CONFIGURE_TEST_SOURCES}") + message("${CONFIGURE_TEST_WRAPPERS}") + + include_directories( + ${CMOCKA_INCLUDE_DIR} + ${CONFIGURE_TEST_INCLUDES} + ) + add_definitions(${CMOCKA_DEFINITIONS}) + + set( + CMAKE_RUNTIME_OUTPUT_DIRECTORY + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test/${CONFIGURE_TEST_SUBPROJECT}/${CONFIGURE_TEST_NAME} + ) + + add_executable(${CONFIGURE_TEST_NAME}_test ${CONFIGURE_TEST_SOURCES}) + + list(LENGTH CONFIGURE_TEST_WRAPPERS WRAPPED_COUNT) + + if(NOT ${WRAPPED_COUNT} STREQUAL "0") + set(WRAPPED "") + + foreach(WRAPPER ${CONFIGURE_TEST_WRAPPERS}) + string(REGEX REPLACE "\\n" "" WRAPPER "${WRAPPER}") + set(WRAPPED + "${WRAPPED} \ + -Wl,--wrap=${WRAPPER}" + ) + message("${WRAPPER}, ${WRAPPED}") + endforeach() + + set_target_properties(${CONFIGURE_TEST_NAME}_test + PROPERTIES + LINK_FLAGS "${WRAPPED}" + ) + endif() + + target_link_libraries(${CONFIGURE_TEST_NAME}_test ${CMOCKA_LIBRARIES} ${CONFIGURE_TEST_LIBRARIES}) +endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index 5280b1d58..e3c50491d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,7 @@ endif() include(FeatureSummary) include(Manpage) +include(Test) include(GNUInstallDirs) if (enable-gdk-pixbuf) From 698ba55860ece9f0065ded257b5ac04531f75f2a Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 17 Jun 2016 07:38:32 -0400 Subject: [PATCH 03/12] Apparently test/ was gitignored --- .gitignore | 1 - test/CMakeLists.txt | 1 + test/common/CMakeLists.txt | 1 + test/common/list/CMakeLists.txt | 7 +++++++ test/common/list/list.c | 19 +++++++++++++++++++ 5 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 test/CMakeLists.txt create mode 100644 test/common/CMakeLists.txt create mode 100644 test/common/list/CMakeLists.txt create mode 100644 test/common/list/list.c diff --git a/.gitignore b/.gitignore index 48f8a0cb0..1ba87a37d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ install_manifest.txt *.o *.a bin/ -test/ build/ .lvimrc config-debug diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 000000000..e4717b2d6 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(common) diff --git a/test/common/CMakeLists.txt b/test/common/CMakeLists.txt new file mode 100644 index 000000000..1487548dc --- /dev/null +++ b/test/common/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(list) diff --git a/test/common/list/CMakeLists.txt b/test/common/list/CMakeLists.txt new file mode 100644 index 000000000..a9b8043b3 --- /dev/null +++ b/test/common/list/CMakeLists.txt @@ -0,0 +1,7 @@ +configure_test( + SUBPROJECT common + NAME list + SOURCES + ${PROJECT_SOURCE_DIR}/common/list.c + list.c +) diff --git a/test/common/list/list.c b/test/common/list/list.c new file mode 100644 index 000000000..dd66dec7a --- /dev/null +++ b/test/common/list/list.c @@ -0,0 +1,19 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include "tests.h" +#include "list.h" + +static void test_test(void **state) { + list_t *list = create_list(); + free(list); + assert_true(true); +} + +int main() { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_test), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} From e563bec64d798c8402d9cc3c13d678525f261584 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 19 Jun 2016 11:25:58 -0400 Subject: [PATCH 04/12] Add memory test utilities --- CMake/Test.cmake | 12 ++++--- CONTRIBUTING.md | 33 ++++++++++++++++++ README.md | 5 +++ include/tests.h | 9 +++++ test/CMakeLists.txt | 6 ++++ test/common/list/list.c | 13 ++++---- test/runner | 11 ++++++ test/util.c | 74 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 153 insertions(+), 10 deletions(-) create mode 100755 test/runner create mode 100644 test/util.c diff --git a/CMake/Test.cmake b/CMake/Test.cmake index 58097529c..a1c27be20 100644 --- a/CMake/Test.cmake +++ b/CMake/Test.cmake @@ -5,8 +5,6 @@ function(configure_test) set(oneValueArgs NAME SUBPROJECT) set(multiValueArgs WRAPPERS SOURCES INCLUDES LIBRARIES) cmake_parse_arguments(CONFIGURE_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - message("${CONFIGURE_TEST_SOURCES}") - message("${CONFIGURE_TEST_WRAPPERS}") include_directories( ${CMOCKA_INCLUDE_DIR} @@ -19,7 +17,12 @@ function(configure_test) ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test/${CONFIGURE_TEST_SUBPROJECT}/${CONFIGURE_TEST_NAME} ) - add_executable(${CONFIGURE_TEST_NAME}_test ${CONFIGURE_TEST_SOURCES}) + add_executable(${CONFIGURE_TEST_NAME}_test + ${CMAKE_SOURCE_DIR}/test/util.c + ${CONFIGURE_TEST_SOURCES} + ) + + list(APPEND CONFIGURE_TEST_WRAPPERS "malloc" "calloc" "realloc" "free") list(LENGTH CONFIGURE_TEST_WRAPPERS WRAPPED_COUNT) @@ -32,7 +35,6 @@ function(configure_test) "${WRAPPED} \ -Wl,--wrap=${WRAPPER}" ) - message("${WRAPPER}, ${WRAPPED}") endforeach() set_target_properties(${CONFIGURE_TEST_NAME}_test @@ -42,4 +44,6 @@ function(configure_test) endif() target_link_libraries(${CONFIGURE_TEST_NAME}_test ${CMOCKA_LIBRARIES} ${CONFIGURE_TEST_LIBRARIES}) + + set(test_targets ${test_targets} ${CONFIGURE_TEST_NAME}_test PARENT_SCOPE) endfunction() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc29dad0d..67694fd3d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,39 @@ branch. Instead, when you start working on a feature, do this: 4. git push -u origin add-so-and-so-feature 5. Make pull request from your feature branch +## Writing Tests + +Tests are driven by [CMocka](https://cmocka.org/). When testing a given +function, we can "mock" out the functions it relies on to program their behavior +explicitly and test the function in isolation. The directory layout of `test/` +is identical to the global directory layout, but each C file in the parent tree +has its own directory in the test tree, with its own CMakeLists.txt that wires +things up. To add a test, make the appropriate directory in `test/` and add a +CMakeLists.txt that looks something like this made-up example: + +```cmake +configure_test( + SUBPROJECT swaymsg + NAME main + SOURCES + ${PROJECT_SOURCE_DIR}/swaymsg/main.c + swaymsg.c + WRAPPERS + ipc_open_socket + LIBRARIES + ${WLC_LIBRARIES} + INCLUDES + ${WLC_INCLUDES} +) +``` + +This defines a test suite in the swaymsg subproject that tests main. This file +would live at `test/swaymsg/main/CMakeLists.txt`. It specifies that it requires +`swaymsg/main.c` and `test/swaymsg/main/swaymsg.c`, the former being the actual +swaymsg source and the latter being the test suite. It mocks ipc_open_socket and +links against openssl. See the cmocka documentation or read existing tests to +learn more about how mocks work. + ## Coding Style Sway is written in C. The style guidelines is [kernel diff --git a/README.md b/README.md index e9143f0fc..c8c531dd1 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,11 @@ On systems without logind, you need to suid the sway binary: sudo chmod a+s /usr/local/bin/sway +## Tests + +Run `make && make check` from the build directory to run tests. The exit code +will be the number of failed tests (0 for success). + ## Configuration If you already use i3, then copy your i3 config to `~/.config/sway/config` and diff --git a/include/tests.h b/include/tests.h index 47b470599..36b685871 100644 --- a/include/tests.h +++ b/include/tests.h @@ -6,4 +6,13 @@ #include #include +enum wrapper_behavior { + WRAPPER_INVOKE_REAL, + WRAPPER_INVOKE_CMOCKA, + WRAPPER_DO_ASSERTIONS, +}; + +int reset_mem_wrappers(void **state); +void memory_behavior(enum wrapper_behavior behavior); + #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e4717b2d6..e60245603 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1 +1,7 @@ +set(test_targets "") + add_subdirectory(common) + +add_custom_target(check + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_SOURCE_DIR}/test/runner) diff --git a/test/common/list/list.c b/test/common/list/list.c index dd66dec7a..7cfc26094 100644 --- a/test/common/list/list.c +++ b/test/common/list/list.c @@ -5,15 +5,16 @@ #include "tests.h" #include "list.h" -static void test_test(void **state) { +static void test_create_list(void **state) { + memory_behavior(WRAPPER_INVOKE_CMOCKA); list_t *list = create_list(); - free(list); - assert_true(true); + assert_int_equal(list->length, 0); + list_free(list); } -int main() { +int main(int argc, char **argv) { const struct CMUnitTest tests[] = { - cmocka_unit_test(test_test), + cmocka_unit_test(test_create_list), }; - return cmocka_run_group_tests(tests, NULL, NULL); + return cmocka_run_group_tests(tests, reset_mem_wrappers, NULL); } diff --git a/test/runner b/test/runner new file mode 100755 index 000000000..cee622f94 --- /dev/null +++ b/test/runner @@ -0,0 +1,11 @@ +#!/usr/bin/bash +tests=$(find . -type f -name "*_test") +ret=0 +for test in $tests +do + printf 'Running %s\n' $(basename $test) + $test + ret+=$? +done + +exit $ret diff --git a/test/util.c b/test/util.c new file mode 100644 index 000000000..ef825200c --- /dev/null +++ b/test/util.c @@ -0,0 +1,74 @@ +#include +#include "tests.h" + +void *__real_malloc(size_t size); +void __real_free(void *ptr); +void *__real_calloc(size_t nmemb, size_t size); +void *__real_realloc(void *ptr, size_t size); + +enum wrapper_behavior _memory_behavior = WRAPPER_INVOKE_REAL; + +int reset_mem_wrappers(void **state) { + _memory_behavior = WRAPPER_INVOKE_REAL; + return 0; +} + +void memory_behavior(enum wrapper_behavior behavior) { + _memory_behavior = behavior; +} + +void *__wrap_malloc(size_t size) { + switch (_memory_behavior) { + case WRAPPER_INVOKE_CMOCKA: + return test_malloc(size); + case WRAPPER_DO_ASSERTIONS: + check_expected(size); + return mock_type(void *); + case WRAPPER_INVOKE_REAL: + default: + return __real_malloc(size); + } +} + +void __wrap_free(void *ptr) { + switch (_memory_behavior) { + case WRAPPER_INVOKE_CMOCKA: + test_free(ptr); + break; + case WRAPPER_DO_ASSERTIONS: + check_expected_ptr(ptr); + break; + case WRAPPER_INVOKE_REAL: + default: + __real_free(ptr); + break; + } +} + +void *__wrap_calloc(size_t nmemb, size_t size) { + switch (_memory_behavior) { + case WRAPPER_INVOKE_CMOCKA: + return test_calloc(nmemb, size); + case WRAPPER_DO_ASSERTIONS: + check_expected(nmemb); + check_expected(size); + return mock_type(void *); + case WRAPPER_INVOKE_REAL: + default: + return __real_calloc(nmemb, size); + } +} + +void *__wrap_realloc(void *ptr, size_t size) { + switch (_memory_behavior) { + case WRAPPER_INVOKE_CMOCKA: + return test_realloc(ptr, size); + case WRAPPER_DO_ASSERTIONS: + check_expected_ptr(ptr); + check_expected(size); + return mock_type(void *); + case WRAPPER_INVOKE_REAL: + default: + return __real_realloc(ptr, size); + } +} From 5b7cb67779e4f2797a86239458486b9fea02170e Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 19 Jun 2016 11:26:33 -0400 Subject: [PATCH 05/12] Run tests from .travis.yml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a8e292ba2..772590d02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,9 +18,11 @@ arch: - cairo - gdk-pixbuf2 - wlc-git + - cmocka script: - "cmake ." - "make" + - "make check" script: - "curl -s https://raw.githubusercontent.com/mikkeloscar/arch-travis/master/arch-travis.sh | bash" From cd64ad56b9edad89288f4b4180074997227685cd Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 19 Jun 2016 12:06:16 -0400 Subject: [PATCH 06/12] Add full coverage for list.c --- include/tests.h | 5 ++ test/common/list/list.c | 175 +++++++++++++++++++++++++++++++++++++++- test/util.c | 36 ++++++++- 3 files changed, 211 insertions(+), 5 deletions(-) diff --git a/include/tests.h b/include/tests.h index 36b685871..de358106a 100644 --- a/include/tests.h +++ b/include/tests.h @@ -14,5 +14,10 @@ enum wrapper_behavior { int reset_mem_wrappers(void **state); void memory_behavior(enum wrapper_behavior behavior); +int malloc_calls(); +int free_calls(); +int calloc_calls(); +int realloc_calls(); +int alloc_calls(); #endif diff --git a/test/common/list/list.c b/test/common/list/list.c index 7cfc26094..9d3077bc3 100644 --- a/test/common/list/list.c +++ b/test/common/list/list.c @@ -5,16 +5,185 @@ #include "tests.h" #include "list.h" -static void test_create_list(void **state) { - memory_behavior(WRAPPER_INVOKE_CMOCKA); +static void assert_list_contents(list_t *list, int contents[], size_t len) { + assert_int_equal(list->length, (int)len); + for (size_t i = 0; i < (size_t)list->length; ++i) { + assert_int_equal(contents[i], *(int *)list->items[i]); + } +} + +static list_t *create_test_list(int contents[], size_t len) { + list_t *l = create_list(); + for (size_t i = 0; i < len; ++i) { + list_add(l, &contents[i]); + } + return l; +} + +static void test_create_and_free(void **state) { list_t *list = create_list(); assert_int_equal(list->length, 0); + assert_int_equal(list->capacity, 10); + assert_non_null(list->items); + list_free(list); + assert_int_equal(malloc_calls(), 2); + assert_int_equal(free_calls(), 2); +} + +static void test_add(void **state) { + list_t *list = create_list(); + + int items[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + for (size_t i = 0; i < sizeof(items) / sizeof(int); ++i) { + list_add(list, &items[i]); + assert_int_equal(items[i], *(int *)list->items[i]); + assert_int_equal(list->length, i + 1); + } + assert_list_contents(list, items, 15); + assert_int_equal(list->length, 15); + assert_int_equal(list->capacity, 20); + assert_int_equal(realloc_calls(), 1); + + list_free(list); +} + +static void test_insert(void **state) { + list_t *list = create_list(); + int i = 1, j = 2; + list_add(list, &i); + list_add(list, &i); + list_add(list, &i); + list_insert(list, 0, &j); + assert_int_equal(j, *(int *)list->items[0]); + assert_int_equal(list->length, 4); + list_free(list); +} + +static void test_del(void **state) { + list_t *list = create_list(); + + int items[] = { 1, 2, 3, 4, 5 }; + int new_items[] = { 1, 2, 4, 5 }; + for (size_t i = 0; i < sizeof(items) / sizeof(int); ++i) { + list_add(list, &items[i]); + } + + list_del(list, 2); + assert_list_contents(list, new_items, 4); + list_free(list); +} + +static void test_cat(void **state) { + int items_a[] = { 1, 2, 3, 4 }; + int items_b[] = { 5, 6, 7, 8 }; + int items_final[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + + list_t *list_a = create_test_list(items_a, 4); + list_t *list_b = create_test_list(items_b, 4); + + list_cat(list_a, list_b); + + assert_list_contents(list_a, items_final, 8); + list_free(list_a); + list_free(list_b); +} + +static int qsort_compare(const void *left, const void *right) { + return **(int * const *)left - **(int * const *)right; +} + +static void test_qsort(void **state) { + int items_start[] = { 1, 4, 3, 2 }; + int items_final[] = { 1, 2, 3, 4 }; + list_t *list = create_test_list(items_start, 4); + list_qsort(list, qsort_compare); + assert_list_contents(list, items_final, 4); + list_free(list); +} + +static int find_compare(const void *a, const void *b) { + return *(int *)a - *(int *)b; +} + +static void test_seq_find(void **state) { + int items[] = { 1, 2, 3, 4 }; + int expected = 3; + list_t *list = create_test_list(items, 4); + int index = list_seq_find(list, find_compare, &expected); + assert_int_equal(index, 2); + list_free(list); +} + +int foreach_count = 0; + +static void foreach(void *item) { + foreach_count++; + assert_int_equal(*(int *)item, foreach_count); +} + +static void test_foreach(void **state) { + int items[] = { 1, 2, 3, 4 }; + list_t *list = create_test_list(items, 4); + list_foreach(list, foreach); + assert_int_equal(foreach_count, 4); + list_free(list); +} + +struct stable_data { + int id, value; +}; + +static int stable_compare(const void *_a, const void *_b) { + struct stable_data * const *a = _a; + struct stable_data * const *b = _b; + return (*a)->value - (*b)->value; +} + +static void test_stable_sort(void **state) { + struct stable_data initial[] = { + { .id = 0, .value = 0 }, + { .id = 3, .value = 2 }, + { .id = 4, .value = 3 }, + { .id = 1, .value = 1 }, + { .id = 2, .value = 1 }, + { .id = 5, .value = 4 }, + { .id = 7, .value = 5 }, + { .id = 6, .value = 5 }, + }; + struct stable_data expected[] = { + { .id = 0, .value = 0 }, + { .id = 1, .value = 1 }, + { .id = 2, .value = 1 }, + { .id = 3, .value = 2 }, + { .id = 4, .value = 3 }, + { .id = 5, .value = 4 }, + { .id = 7, .value = 5 }, + { .id = 6, .value = 5 }, + }; + list_t *list = create_list(); + for (size_t i = 0; i < sizeof(initial) / sizeof(initial[0]); ++i) { + list_add(list, &initial[i]); + } + list_stable_sort(list, stable_compare); + for (size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); ++i) { + struct stable_data *item = list->items[i]; + assert_int_equal(item->value, expected[i].value); + assert_int_equal(item->id, expected[i].id); + } list_free(list); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { - cmocka_unit_test(test_create_list), + cmocka_unit_test(test_create_and_free), + cmocka_unit_test(test_add), + cmocka_unit_test(test_insert), + cmocka_unit_test(test_del), + cmocka_unit_test(test_cat), + cmocka_unit_test(test_qsort), + cmocka_unit_test(test_seq_find), + cmocka_unit_test(test_foreach), + cmocka_unit_test(test_stable_sort), }; return cmocka_run_group_tests(tests, reset_mem_wrappers, NULL); } diff --git a/test/util.c b/test/util.c index ef825200c..837ac3d5d 100644 --- a/test/util.c +++ b/test/util.c @@ -6,10 +6,18 @@ void __real_free(void *ptr); void *__real_calloc(size_t nmemb, size_t size); void *__real_realloc(void *ptr, size_t size); -enum wrapper_behavior _memory_behavior = WRAPPER_INVOKE_REAL; +enum wrapper_behavior _memory_behavior = WRAPPER_INVOKE_CMOCKA; +int malloc_callcount = 0, + free_callcount = 0, + calloc_callcount = 0, + realloc_callcount = 0; int reset_mem_wrappers(void **state) { - _memory_behavior = WRAPPER_INVOKE_REAL; + _memory_behavior = WRAPPER_INVOKE_CMOCKA; + malloc_callcount = + free_callcount = + calloc_callcount = + realloc_callcount = 0; return 0; } @@ -17,7 +25,28 @@ void memory_behavior(enum wrapper_behavior behavior) { _memory_behavior = behavior; } +int malloc_calls() { + return malloc_callcount; +} + +int free_calls() { + return free_callcount; +} + +int calloc_calls() { + return calloc_callcount; +} + +int realloc_calls() { + return realloc_callcount; +} + +int alloc_calls() { + return malloc_callcount + calloc_callcount; +} + void *__wrap_malloc(size_t size) { + ++malloc_callcount; switch (_memory_behavior) { case WRAPPER_INVOKE_CMOCKA: return test_malloc(size); @@ -31,6 +60,7 @@ void *__wrap_malloc(size_t size) { } void __wrap_free(void *ptr) { + ++free_callcount; switch (_memory_behavior) { case WRAPPER_INVOKE_CMOCKA: test_free(ptr); @@ -46,6 +76,7 @@ void __wrap_free(void *ptr) { } void *__wrap_calloc(size_t nmemb, size_t size) { + ++calloc_callcount; switch (_memory_behavior) { case WRAPPER_INVOKE_CMOCKA: return test_calloc(nmemb, size); @@ -60,6 +91,7 @@ void *__wrap_calloc(size_t nmemb, size_t size) { } void *__wrap_realloc(void *ptr, size_t size) { + ++realloc_callcount; switch (_memory_behavior) { case WRAPPER_INVOKE_CMOCKA: return test_realloc(ptr, size); From 074238e9f49712e5b26f5f3f02944adaaf076f35 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 17 Jun 2016 08:46:18 -0400 Subject: [PATCH 07/12] Optionally generate test coverage reports --- CMake/CodeCoverage.cmake | 184 +++++++++++++++++++++++++++++++++++++++ CMake/Test.cmake | 13 ++- CMakeLists.txt | 4 + 3 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 CMake/CodeCoverage.cmake diff --git a/CMake/CodeCoverage.cmake b/CMake/CodeCoverage.cmake new file mode 100644 index 000000000..db9d6c55b --- /dev/null +++ b/CMake/CodeCoverage.cmake @@ -0,0 +1,184 @@ +# Copyright (c) 2012 - 2015, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# USAGE: + +# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: +# http://stackoverflow.com/a/22404544/80480 +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt: +# INCLUDE(CodeCoverage) +# +# 3. Set compiler flags to turn off optimization and enable coverage: +# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") +# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") +# +# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target +# which runs your test executable and produces a lcov code coverage report: +# Example: +# SETUP_TARGET_FOR_COVERAGE( +# my_coverage_target # Name for custom target. +# test_driver # Name of the test driver executable that runs the tests. +# # NOTE! This should always have a ZERO as exit code +# # otherwise the coverage generation will not complete. +# coverage # Name of output directory. +# ) +# +# 4. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# +# + +# Check prereqs +FIND_PROGRAM( GCOV_PATH gcov ) +FIND_PROGRAM( LCOV_PATH lcov ) +FIND_PROGRAM( GENHTML_PATH genhtml ) +FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) + +IF(NOT GCOV_PATH) + MESSAGE(FATAL_ERROR "gcov not found! Aborting...") +ENDIF() # NOT GCOV_PATH + +SET(CMAKE_CXX_FLAGS_COVERAGE + "-g -O0 --coverage -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +SET(CMAKE_C_FLAGS_COVERAGE + "-g -O0 --coverage -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +MARK_AS_ADVANCED( + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) + MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) +ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" + + +# Param _targetname The name of new the custom make target +# Param _testrunner The name of the target which runs the tests. +# MUST return ZERO always, even on errors. +# If not, no coverage report will be created! +# Param _outputname lcov output is generated as _outputname.info +# HTML report is generated in _outputname/index.html +# Optional fourth parameter is passed as arguments to _testrunner +# Pass them in list form, e.g.: "-j;2" for -j 2 +FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) + + IF(NOT LCOV_PATH) + MESSAGE(FATAL_ERROR "lcov not found! Aborting...") + ENDIF() # NOT LCOV_PATH + + IF(NOT GENHTML_PATH) + MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") + ENDIF() # NOT GENHTML_PATH + + # Setup target + ADD_CUSTOM_TARGET(${_targetname} + + # Cleanup lcov + ${LCOV_PATH} --directory . --zerocounters + + # Run tests + COMMAND ${_testrunner} ${ARGV3} + + # Capturing lcov counters and generating report + COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info + COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'tests/*' '/usr/*' --output-file ${_outputname}.info.cleaned + COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned + COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned + + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show info where to find the report + ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD + COMMAND ; + COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." + ) + +ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE + +# Param _targetname The name of new the custom make target +# Param _testrunner The name of the target which runs the tests +# Param _outputname cobertura output is generated as _outputname.xml +# Optional fourth parameter is passed as arguments to _testrunner +# Pass them in list form, e.g.: "-j;2" for -j 2 +FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname) + + IF(NOT PYTHON_EXECUTABLE) + MESSAGE(FATAL_ERROR "Python not found! Aborting...") + ENDIF() # NOT PYTHON_EXECUTABLE + + IF(NOT GCOVR_PATH) + MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") + ENDIF() # NOT GCOVR_PATH + + ADD_CUSTOM_TARGET(${_targetname} + + # Run tests + ${_testrunner} ${ARGV3} + + # Running gcovr + COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." + ) + +ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA diff --git a/CMake/Test.cmake b/CMake/Test.cmake index a1c27be20..b2e9635c7 100644 --- a/CMake/Test.cmake +++ b/CMake/Test.cmake @@ -24,6 +24,10 @@ function(configure_test) list(APPEND CONFIGURE_TEST_WRAPPERS "malloc" "calloc" "realloc" "free") + if (enable-coverage) + add_compile_options(-g -O0 --coverage -fprofile-arcs -ftest-coverage) + endif() + list(LENGTH CONFIGURE_TEST_WRAPPERS WRAPPED_COUNT) if(NOT ${WRAPPED_COUNT} STREQUAL "0") @@ -45,5 +49,12 @@ function(configure_test) target_link_libraries(${CONFIGURE_TEST_NAME}_test ${CMOCKA_LIBRARIES} ${CONFIGURE_TEST_LIBRARIES}) - set(test_targets ${test_targets} ${CONFIGURE_TEST_NAME}_test PARENT_SCOPE) + if (enable-coverage) + target_link_libraries(${CONFIGURE_TEST_NAME}_test gcov) + setup_target_for_coverage( + ${CONFIGURE_TEST_NAME}_cov + ${CONFIGURE_TEST_NAME}_test + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/coverage + ) + endif() endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index e3c50491d..7fe2181bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ option(enable-binding-event "Enables binding event subscription" YES) option(zsh-completions "Zsh shell completions" NO) option(default-wallpaper "Installs the default wallpaper" YES) option(enable-tests "Enables test suite" YES) +option(enable-coverage "Enables test coverage" NO) find_package(JsonC REQUIRED) find_package(PCRE REQUIRED) @@ -134,6 +135,9 @@ install( if(enable-tests) if (CMOCKA_FOUND) + if(enable-coverage) + include(CodeCoverage) + endif() add_subdirectory(test) else() message(WARNING "Not buliding tests - cmocka is required.") From 8758a2bd0490c7859713dab334d19c6940d75798 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 19 Jun 2016 12:28:34 -0400 Subject: [PATCH 08/12] Simplify code coverage implementation --- CMake/CodeCoverage.cmake | 184 --------------------------------------- CMake/Test.cmake | 9 +- CMakeLists.txt | 3 - test/CMakeLists.txt | 17 +++- test/runner | 13 +++ 5 files changed, 30 insertions(+), 196 deletions(-) delete mode 100644 CMake/CodeCoverage.cmake diff --git a/CMake/CodeCoverage.cmake b/CMake/CodeCoverage.cmake deleted file mode 100644 index db9d6c55b..000000000 --- a/CMake/CodeCoverage.cmake +++ /dev/null @@ -1,184 +0,0 @@ -# Copyright (c) 2012 - 2015, Lars Bilke -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors -# may be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# -# -# 2012-01-31, Lars Bilke -# - Enable Code Coverage -# -# 2013-09-17, Joakim Söderberg -# - Added support for Clang. -# - Some additional usage instructions. -# -# USAGE: - -# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: -# http://stackoverflow.com/a/22404544/80480 -# -# 1. Copy this file into your cmake modules path. -# -# 2. Add the following line to your CMakeLists.txt: -# INCLUDE(CodeCoverage) -# -# 3. Set compiler flags to turn off optimization and enable coverage: -# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") -# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") -# -# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target -# which runs your test executable and produces a lcov code coverage report: -# Example: -# SETUP_TARGET_FOR_COVERAGE( -# my_coverage_target # Name for custom target. -# test_driver # Name of the test driver executable that runs the tests. -# # NOTE! This should always have a ZERO as exit code -# # otherwise the coverage generation will not complete. -# coverage # Name of output directory. -# ) -# -# 4. Build a Debug build: -# cmake -DCMAKE_BUILD_TYPE=Debug .. -# make -# make my_coverage_target -# -# - -# Check prereqs -FIND_PROGRAM( GCOV_PATH gcov ) -FIND_PROGRAM( LCOV_PATH lcov ) -FIND_PROGRAM( GENHTML_PATH genhtml ) -FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) - -IF(NOT GCOV_PATH) - MESSAGE(FATAL_ERROR "gcov not found! Aborting...") -ENDIF() # NOT GCOV_PATH - -SET(CMAKE_CXX_FLAGS_COVERAGE - "-g -O0 --coverage -fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the C++ compiler during coverage builds." - FORCE ) -SET(CMAKE_C_FLAGS_COVERAGE - "-g -O0 --coverage -fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the C compiler during coverage builds." - FORCE ) -SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE - "" - CACHE STRING "Flags used for linking binaries during coverage builds." - FORCE ) -SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE - "" - CACHE STRING "Flags used by the shared libraries linker during coverage builds." - FORCE ) -MARK_AS_ADVANCED( - CMAKE_CXX_FLAGS_COVERAGE - CMAKE_C_FLAGS_COVERAGE - CMAKE_EXE_LINKER_FLAGS_COVERAGE - CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) - -IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) - MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) -ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" - - -# Param _targetname The name of new the custom make target -# Param _testrunner The name of the target which runs the tests. -# MUST return ZERO always, even on errors. -# If not, no coverage report will be created! -# Param _outputname lcov output is generated as _outputname.info -# HTML report is generated in _outputname/index.html -# Optional fourth parameter is passed as arguments to _testrunner -# Pass them in list form, e.g.: "-j;2" for -j 2 -FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) - - IF(NOT LCOV_PATH) - MESSAGE(FATAL_ERROR "lcov not found! Aborting...") - ENDIF() # NOT LCOV_PATH - - IF(NOT GENHTML_PATH) - MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") - ENDIF() # NOT GENHTML_PATH - - # Setup target - ADD_CUSTOM_TARGET(${_targetname} - - # Cleanup lcov - ${LCOV_PATH} --directory . --zerocounters - - # Run tests - COMMAND ${_testrunner} ${ARGV3} - - # Capturing lcov counters and generating report - COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info - COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'tests/*' '/usr/*' --output-file ${_outputname}.info.cleaned - COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned - COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned - - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." - ) - - # Show info where to find the report - ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD - COMMAND ; - COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." - ) - -ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE - -# Param _targetname The name of new the custom make target -# Param _testrunner The name of the target which runs the tests -# Param _outputname cobertura output is generated as _outputname.xml -# Optional fourth parameter is passed as arguments to _testrunner -# Pass them in list form, e.g.: "-j;2" for -j 2 -FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname) - - IF(NOT PYTHON_EXECUTABLE) - MESSAGE(FATAL_ERROR "Python not found! Aborting...") - ENDIF() # NOT PYTHON_EXECUTABLE - - IF(NOT GCOVR_PATH) - MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") - ENDIF() # NOT GCOVR_PATH - - ADD_CUSTOM_TARGET(${_targetname} - - # Run tests - ${_testrunner} ${ARGV3} - - # Running gcovr - COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Running gcovr to produce Cobertura code coverage report." - ) - - # Show info where to find the report - ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD - COMMAND ; - COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." - ) - -ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA diff --git a/CMake/Test.cmake b/CMake/Test.cmake index b2e9635c7..18f5f919c 100644 --- a/CMake/Test.cmake +++ b/CMake/Test.cmake @@ -1,5 +1,3 @@ -find_package(A2X REQUIRED) - function(configure_test) set(options) set(oneValueArgs NAME SUBPROJECT) @@ -25,7 +23,7 @@ function(configure_test) list(APPEND CONFIGURE_TEST_WRAPPERS "malloc" "calloc" "realloc" "free") if (enable-coverage) - add_compile_options(-g -O0 --coverage -fprofile-arcs -ftest-coverage) + add_definitions(-g -O0 --coverage -fprofile-arcs -ftest-coverage) endif() list(LENGTH CONFIGURE_TEST_WRAPPERS WRAPPED_COUNT) @@ -51,10 +49,5 @@ function(configure_test) if (enable-coverage) target_link_libraries(${CONFIGURE_TEST_NAME}_test gcov) - setup_target_for_coverage( - ${CONFIGURE_TEST_NAME}_cov - ${CONFIGURE_TEST_NAME}_test - ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/coverage - ) endif() endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index 7fe2181bf..e0f014aa0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,9 +135,6 @@ install( if(enable-tests) if (CMOCKA_FOUND) - if(enable-coverage) - include(CodeCoverage) - endif() add_subdirectory(test) else() message(WARNING "Not buliding tests - cmocka is required.") diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e60245603..841b25aee 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,4 +4,19 @@ add_subdirectory(common) add_custom_target(check WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} - COMMAND ${CMAKE_SOURCE_DIR}/test/runner) + COMMAND ${CMAKE_SOURCE_DIR}/test/runner "${CMAKE_BINARY_DIR}") + +if(enable-coverage) + find_program(GCOV_PATH gcov) + find_program(LCOV_PATH lcov) + find_program(GENHTML_PATH genhtml) + if(NOT GCOV_PATH) + MESSAGE(FATAL_ERROR "gcov not found! Aborting...") + endif() + if(NOT LCOV_PATH) + MESSAGE(FATAL_ERROR "gcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + MESSAGE(FATAL_ERROR "gcov not found! Aborting...") + endif() +endif() diff --git a/test/runner b/test/runner index cee622f94..43ab45e50 100755 --- a/test/runner +++ b/test/runner @@ -8,4 +8,17 @@ do ret+=$? done +if grep 'enable-coverage:BOOL=YES' "$1/CMakeCache.txt" +then + echo "Generating coverage reports" + rm -rf "$1/coverage" + mkdir "$1/coverage" + lcov --directory "$1" \ + --capture \ + --output-file "$1/coverage/lcov.info" + lcov --remove "$1/coverage/lcov.info" 'test/*' '/usr/*' \ + --output-file "$1/coverage/lcov.info.clean" + genhtml -o "$1/coverage/" "$1/coverage/lcov.info.clean" +fi + exit $ret From 689935ed399b778841903ea8107ad13345f920f8 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 19 Jun 2016 12:48:20 -0400 Subject: [PATCH 09/12] Add readline tests --- common/readline.c | 4 +- test/common/CMakeLists.txt | 1 + test/common/list/list.c | 2 - test/common/readline/CMakeLists.txt | 9 +++++ test/common/readline/readline.c | 61 +++++++++++++++++++++++++++++ test/runner | 23 ++++++----- 6 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 test/common/readline/CMakeLists.txt create mode 100644 test/common/readline/readline.c diff --git a/common/readline.c b/common/readline.c index 5106172c3..5815c3f14 100644 --- a/common/readline.c +++ b/common/readline.c @@ -10,7 +10,7 @@ char *read_line(FILE *file) { return NULL; } while (1) { - int c = getc(file); + int c = fgetc(file); if (c == '\n' && lastChar == '\\'){ --length; // Ignore last character. lastChar = '\0'; @@ -51,7 +51,7 @@ char *read_line_buffer(FILE *file, char *string, size_t string_len) { return NULL; } while (1) { - int c = getc(file); + int c = fgetc(file); if (c == EOF || c == '\n' || c == '\0') { break; } diff --git a/test/common/CMakeLists.txt b/test/common/CMakeLists.txt index 1487548dc..8226b207b 100644 --- a/test/common/CMakeLists.txt +++ b/test/common/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(list) +add_subdirectory(readline) diff --git a/test/common/list/list.c b/test/common/list/list.c index 9d3077bc3..105e2e818 100644 --- a/test/common/list/list.c +++ b/test/common/list/list.c @@ -1,6 +1,4 @@ #define _POSIX_C_SOURCE 200809L -#include -#include #include #include "tests.h" #include "list.h" diff --git a/test/common/readline/CMakeLists.txt b/test/common/readline/CMakeLists.txt new file mode 100644 index 000000000..0327c4265 --- /dev/null +++ b/test/common/readline/CMakeLists.txt @@ -0,0 +1,9 @@ +configure_test( + SUBPROJECT common + NAME readline + SOURCES + ${PROJECT_SOURCE_DIR}/common/readline.c + readline.c + WRAPPERS + fgetc +) diff --git a/test/common/readline/readline.c b/test/common/readline/readline.c new file mode 100644 index 000000000..ba182fbd9 --- /dev/null +++ b/test/common/readline/readline.c @@ -0,0 +1,61 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include "tests.h" +#include "readline.h" + +int __wrap_fgetc(FILE *stream) { + return mock_type(int); +} + +static void prep_string(const char *str) { + while (*str) { + will_return(__wrap_fgetc, *str++); + } +} + +static void test_eof_line_ending(void **state) { + prep_string("hello"); + will_return(__wrap_fgetc, EOF); + char *line = read_line(NULL); + assert_string_equal(line, "hello"); + free(line); +} + +static void test_newline(void **state) { + prep_string("hello\n"); + char *line = read_line(NULL); + assert_string_equal(line, "hello"); + free(line); +} + +static void test_continuation(void **state) { + prep_string("hello \\\nworld"); + will_return(__wrap_fgetc, EOF); + char *line = read_line(NULL); + assert_string_equal(line, "hello world"); + free(line); +} + +static void test_expand_buffer(void **state) { + const char *test = "This is a very very long string. " + "This string is so long that it may in fact be greater " + "than 128 bytes (or octets) in length, which is suitable " + "for triggering a realloc"; + prep_string(test); + will_return(__wrap_fgetc, EOF); + char *line = read_line(NULL); + assert_string_equal(line, test); + free(line); + assert_int_equal(realloc_calls(), 1); +} + +int main(int argc, char **argv) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_eof_line_ending), + cmocka_unit_test(test_newline), + cmocka_unit_test(test_continuation), + cmocka_unit_test(test_expand_buffer), + }; + return cmocka_run_group_tests(tests, reset_mem_wrappers, NULL); +} diff --git a/test/runner b/test/runner index 43ab45e50..35004f5a1 100755 --- a/test/runner +++ b/test/runner @@ -8,17 +8,20 @@ do ret+=$? done -if grep 'enable-coverage:BOOL=YES' "$1/CMakeCache.txt" +if (( $ret == 0 )) then - echo "Generating coverage reports" - rm -rf "$1/coverage" - mkdir "$1/coverage" - lcov --directory "$1" \ - --capture \ - --output-file "$1/coverage/lcov.info" - lcov --remove "$1/coverage/lcov.info" 'test/*' '/usr/*' \ - --output-file "$1/coverage/lcov.info.clean" - genhtml -o "$1/coverage/" "$1/coverage/lcov.info.clean" + if grep 'enable-coverage:BOOL=YES' "$1/CMakeCache.txt" + then + echo "Generating coverage reports" + rm -rf "$1/coverage" + mkdir "$1/coverage" + lcov --directory "$1" \ + --capture \ + --output-file "$1/coverage/lcov.info" + lcov --remove "$1/coverage/lcov.info" 'test/*' '/usr/*' \ + --output-file "$1/coverage/lcov.info.clean" + genhtml -o "$1/coverage/" "$1/coverage/lcov.info.clean" + fi fi exit $ret From 439d289654bb125c87e1673c6cb98b01013ef342 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 19 Jun 2016 12:51:44 -0400 Subject: [PATCH 10/12] Fix typo --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e0f014aa0..4219bc9eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ if(enable-tests) if (CMOCKA_FOUND) add_subdirectory(test) else() - message(WARNING "Not buliding tests - cmocka is required.") + message(WARNING "Not building tests - cmocka is required.") endif() endif() From 2aaa37b129a06f9a5aa5e11612aae556846ce65a Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 19 Jun 2016 12:52:44 -0400 Subject: [PATCH 11/12] Fix cmake error messages for gcov/lcov/etc --- test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 841b25aee..b6d757a5f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,9 +14,9 @@ if(enable-coverage) MESSAGE(FATAL_ERROR "gcov not found! Aborting...") endif() if(NOT LCOV_PATH) - MESSAGE(FATAL_ERROR "gcov not found! Aborting...") + MESSAGE(FATAL_ERROR "lcov not found! Aborting...") endif() if(NOT GENHTML_PATH) - MESSAGE(FATAL_ERROR "gcov not found! Aborting...") + MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") endif() endif() From d47a9d7752d6a1faa201005f9c4d7f4588fad877 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 19 Jun 2016 13:51:32 -0400 Subject: [PATCH 12/12] Quiet down the coverage output a bit --- test/runner | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/runner b/test/runner index 35004f5a1..7baf032d4 100755 --- a/test/runner +++ b/test/runner @@ -10,16 +10,16 @@ done if (( $ret == 0 )) then - if grep 'enable-coverage:BOOL=YES' "$1/CMakeCache.txt" + if grep 'enable-coverage:BOOL=YES' "$1/CMakeCache.txt" > /dev/null then echo "Generating coverage reports" rm -rf "$1/coverage" mkdir "$1/coverage" lcov --directory "$1" \ --capture \ - --output-file "$1/coverage/lcov.info" + --output-file "$1/coverage/lcov.info" > /dev/null lcov --remove "$1/coverage/lcov.info" 'test/*' '/usr/*' \ - --output-file "$1/coverage/lcov.info.clean" + --output-file "$1/coverage/lcov.info.clean" > /dev/null genhtml -o "$1/coverage/" "$1/coverage/lcov.info.clean" fi fi