From 2234fa433e9e1b343e7b189472804bc431eabbb0 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sat, 21 Oct 2023 11:05:03 -0700 Subject: [PATCH 01/12] Add Asan build type that enables the Address and UB sanitizers. Uses generator statements instead of CMAKE__FLAGS_ASAN to support multiconfig generators like Xcode. --- CMakeLists.txt | 17 +++++++++++ bindings/python/tests/CMakeLists.txt | 10 +++---- .../cmake_modules/GncAddSchemeTargets.cmake | 17 +++++++---- common/cmake_modules/GncAddTest.cmake | 29 +++++++++---------- libgnucash/backend/xml/test/CMakeLists.txt | 2 +- 5 files changed, 48 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd9148a523..c67221a98e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -610,6 +610,23 @@ if (MINGW) set( CMAKE_CXX_FLAGS "-DWINVER=0x0500 -D_EMULATE_GLIBC=0 ${CMAKE_CXX_FLAGS}") # Workaround for bug in gtest on mingw, see https://github.com/google/googletest/issues/893 and https://github.com/google/googletest/issues/920 endif() +if (APPLE) + execute_process(COMMAND clang --print-file-name=libclang_rt.asan_osx_dynamic.dylib + OUTPUT_VARIABLE ASAN_DYNAMIC_LIB + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(ASAN_DYNAMIC_LIB_ENV "DYLD_INSERT_LIBRARIES=${ASAN_DYNAMIC_LIB}") +elseif(UNIX) + execute_process(COMMAND gcc -print-file-name=libasan.so OUTPUT_VARIABLE LIBASAN_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND gcc -print-file-name=libstdc++.so OUTPUT_VARIABLE LIBSTDCXX_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) + set(PRELOADS "${LIBASAN_PATH} ${LIBSTDCXX_PATH}") + set(ASAN_OPTIONS "detect_leaks=0:fast_unwind_on_malloc=0") + set(ASAN_DYNAMIC_LIB_ENV "LD_PRELOAD=${PRELOADS};ASAN_OPTIONS=${ASAN_OPTIONS}") +endif () +set(ASAN_BUILD_OPTIONS -fsanitize=address -fsanitize=undefined) +set(ASAN_COMPILE_OPTIONS -g ${ASAN_BUILD_OPTIONS}) +add_compile_options("$<$:${ASAN_COMPILE_OPTIONS}>") +add_link_options("$<$:${ASAN_BUILD_OPTIONS}>") + if (APPLE AND WITH_GNUCASH) set(CMAKE_MACOSX_RPATH ON) set(CMAKE_INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}") diff --git a/bindings/python/tests/CMakeLists.txt b/bindings/python/tests/CMakeLists.txt index 9ec9ca29a9..77733f8804 100644 --- a/bindings/python/tests/CMakeLists.txt +++ b/bindings/python/tests/CMakeLists.txt @@ -7,11 +7,11 @@ if (WITH_PYTHON) endif() add_custom_target(test-python-bindings ALL DEPENDS unittest_support gnucash-core-c-build gnucash-core-c-py sw-core-utils-build sw-core-utils-py sw-app-utils-build sw-app-utils-py) add_dependencies(check test-python-bindings) - add_test(python-bindings ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/runTests.py.in) - set_property(TEST python-bindings PROPERTY ENVIRONMENT - GNC_BUILDDIR=${CMAKE_BINARY_DIR} - PYTHONPATH=${PYTHON_SYSCONFIG_BUILD}:${LIBDIR_BUILD}/gnucash:${test_core_dir} - ) + add_test(NAME python-bindings COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/runTests.py.in) + set(PYTHON_ENV "GNC_UNINSTALLED=1;GNC_BUILDDIR=${CMAKE_BINARY_DIR};PYTHONPATH=${PYTHON_SYSCONFIG_BUILD}:${LIBDIR_BUILD}/gnucash:${test_core_dir}") + set(ASAN_ENV "${ASAN_DYNAMIC_LIB_ENV};ASAN_OPTIONS=fast_unwind_on_malloc=0") + set_tests_properties(python-bindings PROPERTIES ENVIRONMENT "$,${PYTHON_ENV};${ASAN_ENV},${PYTHON_ENV}>") + endif() set(test_python_bindings_DATA diff --git a/common/cmake_modules/GncAddSchemeTargets.cmake b/common/cmake_modules/GncAddSchemeTargets.cmake index 63c1b3d205..bf86629b04 100644 --- a/common/cmake_modules/GncAddSchemeTargets.cmake +++ b/common/cmake_modules/GncAddSchemeTargets.cmake @@ -266,18 +266,23 @@ function(gnc_add_scheme_targets _TARGET) message(" GNC_MODULE_PATH: ${_GNC_MODULE_PATH}") endif() #We quote the arguments to stop CMake stripping the path separators. + set (GUILE_ENV + "${LIBRARY_PATH}" + "GNC_UNINSTALLED=YES" + "GNC_BUILDDIR=${CMAKE_BINARY_DIR}" + "GUILE_LOAD_PATH=${_GUILE_LOAD_PATH}" + "GUILE_LOAD_COMPILED_PATH=${_GUILE_LOAD_COMPILED_PATH}" + "GNC_MODULE_PATH=${_GNC_MODULE_PATH}" + ) + add_custom_command( OUTPUT ${output_file} COMMAND ${CMAKE_COMMAND} -E env - "${LIBRARY_PATH}" - "GNC_UNINSTALLED=YES" - "GNC_BUILDDIR=${CMAKE_BINARY_DIR}" - "GUILE_LOAD_PATH=${_GUILE_LOAD_PATH}" - "GUILE_LOAD_COMPILED_PATH=${_GUILE_LOAD_COMPILED_PATH}" - "GNC_MODULE_PATH=${_GNC_MODULE_PATH}" + "${GUILE_ENV}$<$:;${ASAN_DYNAMIC_LIB_ENV}>" ${GUILE_EXECUTABLE} -e "\(@@ \(guild\) main\)" -s ${GUILD_EXECUTABLE} compile -o ${output_file} ${source_file_abs_path} DEPENDS ${guile_depends} MAIN_DEPENDENCY ${source_file_abs_path} + COMMAND_EXPAND_LISTS VERBATIM ) endforeach(source_file) diff --git a/common/cmake_modules/GncAddTest.cmake b/common/cmake_modules/GncAddTest.cmake index 6d095afc4b..bf40e3e758 100644 --- a/common/cmake_modules/GncAddTest.cmake +++ b/common/cmake_modules/GncAddTest.cmake @@ -77,36 +77,35 @@ function(gnc_add_test _TARGET _SOURCE_FILES TEST_INCLUDE_VAR_NAME TEST_LIBS_VAR_ # Extra arguments are treated as environment variables set(HAVE_ENV_VARS TRUE) endif() + set(ENVVARS "GNC_UNINSTALLED=YES;GNC_BUILDDIR=${CMAKE_BINARY_DIR}") + if (HAVE_ENV_VARS) + list(APPEND ENVVARS ${ARGN}) + endif() set(TEST_INCLUDE_DIRS ${${TEST_INCLUDE_VAR_NAME}}) set(TEST_LIBS ${${TEST_LIBS_VAR_NAME}}) set_source_files_properties (${_SOURCE_FILES} PROPERTIES OBJECT_DEPENDS ${CONFIG_H}) - add_executable(${_TARGET} EXCLUDE_FROM_ALL ${_SOURCE_FILES}) - target_link_libraries(${_TARGET} ${TEST_LIBS}) - target_include_directories(${_TARGET} PRIVATE ${TEST_INCLUDE_DIRS}) - if (${HAVE_ENV_VARS}) - add_test(${_TARGET} ${CMAKE_BINARY_DIR}/bin/${_TARGET}) - set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "GNC_UNINSTALLED=YES;GNC_BUILDDIR=${CMAKE_BINARY_DIR};${ARGN}") + if (CMAKE_GENERATOR STREQUAL Xcode) + add_test(NAME ${_TARGET} COMMAND ${_TARGET} CONFIGURATIONS Debug;Release) else() - if (CMAKE_GENERATOR STREQUAL Xcode) - add_test(NAME ${_TARGET} COMMAND ${_TARGET} CONFIGURATIONS Debug;Release) - else() - add_test(NAME ${_TARGET} COMMAND ${_TARGET}) - endif() - set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "GNC_UNINSTALLED=YES;GNC_BUILDDIR=${CMAKE_BINARY_DIR}") + add_test(NAME ${_TARGET} COMMAND ${_TARGET}) endif() + add_executable(${_TARGET} EXCLUDE_FROM_ALL ${_SOURCE_FILES}) + target_link_libraries(${_TARGET} PRIVATE ${TEST_LIBS}) + target_include_directories(${_TARGET} PRIVATE ${TEST_INCLUDE_DIRS}) + set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "$,${ENVVARS};ASAN_OPTIONS=fast_unwind_on_malloc=0,${ENVVARS}>") add_dependencies(check ${_TARGET}) endfunction() function(gnc_add_test_with_guile _TARGET _SOURCE_FILES TEST_INCLUDE_VAR_NAME TEST_LIBS_VAR_NAME) get_guile_env() gnc_add_test(${_TARGET} "${_SOURCE_FILES}" "${TEST_INCLUDE_VAR_NAME}" "${TEST_LIBS_VAR_NAME}" - "${GUILE_ENV};${ARGN}" + "${GUILE_ENV}$<$:;${ASAN_DYNAMIC_LIB_ENV}>;${ARGN}" ) endfunction() function(gnc_add_scheme_test _TARGET _SOURCE_FILE) - add_test(${_TARGET} ${GUILE_EXECUTABLE} --debug -c " + add_test(NAME ${_TARGET} COMMAND ${GUILE_EXECUTABLE} --debug -c " (set! %load-hook (lambda (filename) (when (and filename @@ -119,7 +118,7 @@ function(gnc_add_scheme_test _TARGET _SOURCE_FILE) (exit (run-test))" ) get_guile_env() - set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "${GUILE_ENV};${ARGN}") + set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "$,${GUILE_ENV};${ASAN_DYNAMIC_LIB_ENV};ASAN_OPTIONS=fast_unwind_on_malloc=0;${ARGN},${GUILE_ENV};${ARGN}>") endfunction() function(gnc_add_scheme_tests _SOURCE_FILES) diff --git a/libgnucash/backend/xml/test/CMakeLists.txt b/libgnucash/backend/xml/test/CMakeLists.txt index 58b537e738..7b11268a6b 100644 --- a/libgnucash/backend/xml/test/CMakeLists.txt +++ b/libgnucash/backend/xml/test/CMakeLists.txt @@ -13,7 +13,7 @@ set(XML_TEST_INCLUDE_DIRS ) -set(XML_TEST_LIBS gnc-engine gnc-test-engine test-core ${LIBXML2_LDFLAGS} -lz) +set(XML_TEST_LIBS gnc-backend-xml-utils gnc-engine gnc-test-engine test-core ${LIBXML2_LDFLAGS} -lz) set(XML_GTEST_LIBS ${XML_TEST_LIBS} gtest) function(add_xml_test _TARGET _SOURCE_FILES) From e17ba3cc002bb19f1eb6a966462ce83bb3fa1959 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sun, 22 Oct 2023 10:15:44 -0700 Subject: [PATCH 02/12] Fix UAF in xaccFreeSplit. xaccSplitComputeCapGains creates gains_split pointers in both the Cap Gains Split and its Income split to the original split, but the original's gains_split pointer can point to only one of them, the Cap Gains split. When the original split is freed both the Cap Gains split's and its Income split need their gains_split pointers NULLed or when it's the Income split's turn to be freed it will try to deref the dangling pointer. --- bindings/guile/gnc-optiondb.i | 4 ++-- libgnucash/backend/dbi/gnc-backend-dbi.cpp | 4 ++-- libgnucash/backend/xml/gnc-xml-backend.cpp | 7 ++++-- libgnucash/engine/Split.c | 11 +++++++--- libgnucash/engine/gnc-date.cpp | 7 ++++-- libgnucash/engine/gnc-timezone.cpp | 4 ++-- libgnucash/engine/qofbook.cpp | 25 +++++++++++++++++----- libgnucash/engine/test/test-guid.cpp | 2 +- libgnucash/engine/test/test-qofbook.c | 3 --- libgnucash/engine/test/test-qofobject.c | 6 +++--- 10 files changed, 48 insertions(+), 25 deletions(-) diff --git a/bindings/guile/gnc-optiondb.i b/bindings/guile/gnc-optiondb.i index 3340e24637..c2ac2e432a 100644 --- a/bindings/guile/gnc-optiondb.i +++ b/bindings/guile/gnc-optiondb.i @@ -1725,13 +1725,13 @@ gnc_register_multichoice_callback_option(GncOptionDBPtr& db, static void test_book_set_data(QofBook* book, const char* key, void* data) { - qof_book_set_data(book, key, data); + qof_book_set_data(book, key, data); } static void test_book_clear_data(QofBook* book, const char* key) { - qof_book_set_data(book, key, nullptr); + qof_book_set_data(book, key, nullptr); } static void diff --git a/libgnucash/backend/dbi/gnc-backend-dbi.cpp b/libgnucash/backend/dbi/gnc-backend-dbi.cpp index 1235ad1b9f..f4b83b4117 100644 --- a/libgnucash/backend/dbi/gnc-backend-dbi.cpp +++ b/libgnucash/backend/dbi/gnc-backend-dbi.cpp @@ -1030,7 +1030,7 @@ template<> bool QofDbiBackendProvider::type_check(const char *uri) { FILE* f; - gchar buf[50]; + gchar buf[51]{}; G_GNUC_UNUSED size_t chars_read; gint status; gchar* filename; @@ -1050,7 +1050,7 @@ QofDbiBackendProvider::type_check(const char *uri) } // OK if file has the correct header - chars_read = fread (buf, sizeof (buf), 1, f); + chars_read = fread (buf, sizeof (buf) - 1, 1, f); status = fclose (f); if (status < 0) { diff --git a/libgnucash/backend/xml/gnc-xml-backend.cpp b/libgnucash/backend/xml/gnc-xml-backend.cpp index 7894d02f57..6f48e574f1 100644 --- a/libgnucash/backend/xml/gnc-xml-backend.cpp +++ b/libgnucash/backend/xml/gnc-xml-backend.cpp @@ -234,7 +234,9 @@ GncXmlBackend::load(QofBook* book, QofBackendLoadType loadType) if (loadType != LOAD_TYPE_INITIAL_LOAD) return; error = ERR_BACKEND_NO_ERR; - m_book = book; + if (m_book) + g_object_unref(m_book); + m_book = QOF_BOOK(g_object_ref(book)); int rc; switch (determine_file_type (m_fullpath)) @@ -306,7 +308,8 @@ GncXmlBackend::sync(QofBook* book) * for multiple books have been removed in the meantime and there is just one * book, no more. */ - if (m_book == nullptr) m_book = book; + if (m_book == nullptr) + m_book = QOF_BOOK(g_object_ref(book)); if (book != m_book) return; if (qof_book_is_readonly (m_book)) diff --git a/libgnucash/engine/Split.c b/libgnucash/engine/Split.c index 252babe507..424ae16a9a 100644 --- a/libgnucash/engine/Split.c +++ b/libgnucash/engine/Split.c @@ -720,9 +720,14 @@ xaccFreeSplit (Split *split) split->date_reconciled = 0; G_OBJECT_CLASS (QOF_INSTANCE_GET_CLASS (&split->inst))->dispose(G_OBJECT (split)); - // Is this right? - if (split->gains_split) split->gains_split->gains_split = NULL; - /* qof_instance_release(&split->inst); */ + + if (split->gains_split) + { + Split *other = xaccSplitGetOtherSplit(split->gains_split); + split->gains_split->gains_split = NULL; + other->gains_split = NULL; + } + g_object_unref(split); } diff --git a/libgnucash/engine/gnc-date.cpp b/libgnucash/engine/gnc-date.cpp index 3dc541313d..e7f0d49aea 100644 --- a/libgnucash/engine/gnc-date.cpp +++ b/libgnucash/engine/gnc-date.cpp @@ -341,7 +341,8 @@ gnc_date_string_to_dateformat(const char* fmt_str, QofDateFormat *format) const char* gnc_date_monthformat_to_string(GNCDateMonthFormat format) { - switch (format) + //avoid UB if format is out of range + switch (static_cast(format)) { case GNCDATE_MONTH_NUMBER: return "number"; @@ -438,7 +439,9 @@ QofDateFormat qof_date_format_get (void) void qof_date_format_set(QofDateFormat df) { - if (df >= DATE_FORMAT_FIRST && df <= DATE_FORMAT_LAST) +//avoid UB if df is out of range + auto dfi{static_cast(df)}; + if (dfi >= DATE_FORMAT_FIRST && dfi <= DATE_FORMAT_LAST) { prevQofDateFormat = dateFormat; dateFormat = df; diff --git a/libgnucash/engine/gnc-timezone.cpp b/libgnucash/engine/gnc-timezone.cpp index b1db4769b4..5584e46e51 100644 --- a/libgnucash/engine/gnc-timezone.cpp +++ b/libgnucash/engine/gnc-timezone.cpp @@ -477,8 +477,8 @@ namespace IANAParser endian_swap(&info.gmtoff); tzinfo.push_back( {info, &fileblock[abbrev + info.abbrind], - fileblock[std_dist + index] != '\0', - fileblock[gmt_dist + index] != '\0'}); + (index < isstd_count ? fileblock[std_dist + index] != '\0' : true), + (index < isgmt_count ? fileblock[gmt_dist + index] != '\0' : false)}); } } diff --git a/libgnucash/engine/qofbook.cpp b/libgnucash/engine/qofbook.cpp index 13bdb71f5d..7575f7a72f 100644 --- a/libgnucash/engine/qofbook.cpp +++ b/libgnucash/engine/qofbook.cpp @@ -32,6 +32,7 @@ * Copyright (c) 2000 Dave Peticolas * Copyright (c) 2007 David Hampton */ +#include "qof-string-cache.h" #include #include @@ -53,6 +54,7 @@ #include "qofobject-p.h" #include "qofbookslots.h" #include "kvp-frame.hpp" +#include "gnc-lot.h" // For GNC_ID_ROOT_ACCOUNT: #include "AccountP.h" @@ -111,7 +113,8 @@ qof_book_init (QofBook *book) qof_instance_init_data (&book->inst, QOF_ID_BOOK, book); - book->data_tables = g_hash_table_new (g_str_hash, g_str_equal); + book->data_tables = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify)qof_string_cache_remove, NULL); book->data_table_finalizers = g_hash_table_new (g_str_hash, g_str_equal); book->book_open = 'y'; @@ -317,6 +320,13 @@ qof_book_finalize_real (G_GNUC_UNUSED GObject *bookp) { } +static void +destroy_lot(QofInstance *inst, [[maybe_unused]]void* data) +{ + auto lot{GNC_LOT(inst)}; + gnc_lot_destroy(lot); +} + void qof_book_destroy (QofBook *book) { @@ -333,6 +343,11 @@ qof_book_destroy (QofBook *book) */ g_hash_table_foreach (book->data_table_finalizers, book_final, book); + /* Lots hold a variety of pointers that need to still exist while + * cleaning them up so run its book_end before the rest. + */ + auto lots{qof_book_get_collection(book, GNC_ID_LOT)}; + qof_collection_foreach(lots, destroy_lot, nullptr); qof_object_book_end (book); g_hash_table_destroy (book->data_table_finalizers); @@ -350,7 +365,6 @@ qof_book_destroy (QofBook *book) cols = book->hash_of_collections; g_object_unref (book); g_hash_table_destroy (cols); - /*book->hash_of_collections = NULL;*/ LEAVE ("book=%p", book); } @@ -450,13 +464,14 @@ qof_book_set_backend (QofBook *book, QofBackend *be) /* ====================================================================== */ /* Store arbitrary pointers in the QofBook for data storage extensibility */ -/* XXX if data is NULL, we should remove the key from the hash table! - */ void qof_book_set_data (QofBook *book, const char *key, gpointer data) { if (!book || !key) return; - g_hash_table_insert (book->data_tables, (gpointer)key, data); + if (data) + g_hash_table_insert (book->data_tables, (gpointer)CACHE_INSERT(key), data); + else + g_hash_table_remove(book->data_tables, key); } void diff --git a/libgnucash/engine/test/test-guid.cpp b/libgnucash/engine/test/test-guid.cpp index 2f14d2bc66..78a4e5c4f6 100644 --- a/libgnucash/engine/test/test-guid.cpp +++ b/libgnucash/engine/test/test-guid.cpp @@ -82,7 +82,7 @@ run_test (void) auto ent = QOF_INSTANCE(g_object_new(QOF_TYPE_INSTANCE, "guid", &guid, NULL)); do_test ((NULL == qof_collection_lookup_entity (col, &guid)), "duplicate guid"); - ent->e_type = type; + ent->e_type = CACHE_INSERT(type); qof_collection_insert_entity (col, ent); do_test ((NULL != qof_collection_lookup_entity (col, &guid)), "guid not found"); diff --git a/libgnucash/engine/test/test-qofbook.c b/libgnucash/engine/test/test-qofbook.c index f131bcbb4c..e74d37a7e7 100644 --- a/libgnucash/engine/test/test-qofbook.c +++ b/libgnucash/engine/test/test-qofbook.c @@ -962,10 +962,7 @@ test_book_new_destroy( void ) qof_book_set_data_fin( book, key, (gpointer) data, mock_final_cb ); test_struct.called = FALSE; - g_test_message( "Testing book destroy" ); qof_book_destroy( book ); - g_assert_true( qof_book_shutting_down( book ) ); - g_assert_true( test_struct.called ); } void diff --git a/libgnucash/engine/test/test-qofobject.c b/libgnucash/engine/test/test-qofobject.c index fae1379587..75d41c452a 100644 --- a/libgnucash/engine/test/test-qofobject.c +++ b/libgnucash/engine/test/test-qofobject.c @@ -303,8 +303,8 @@ test_qof_object_book_begin( Fixture *fixture, gconstpointer pData ) g_assert_cmpint( g_list_index( get_book_list(), (gconstpointer) book2 ), != , -1 ); g_assert_cmpint( object_book_begin_struct.call_count, == , list_length ); - qof_object_foreach_type ((QofForeachTypeCB)g_free, NULL); qof_book_destroy( book2 ); + qof_object_foreach_type ((QofForeachTypeCB)g_free, NULL); } static void @@ -389,8 +389,8 @@ test_qof_object_is_dirty( Fixture *fixture, gconstpointer pData ) g_assert_true( qof_object_is_dirty( book ) == TRUE ); g_assert_cmpint( object_dirty_struct.call_count, == , 1 ); /* should break on first */ - qof_object_foreach_type ((QofForeachTypeCB)g_free, NULL); qof_book_destroy( book ); + qof_object_foreach_type ((QofForeachTypeCB)g_free, NULL); } static struct @@ -433,8 +433,8 @@ test_qof_object_mark_clean( Fixture *fixture, gconstpointer pData ) qof_object_mark_clean( book ); g_assert_cmpint( object_mark_clean_struct.call_count, == , list_length ); - qof_object_foreach_type ((QofForeachTypeCB)g_free, NULL); qof_book_destroy( book ); + qof_object_foreach_type ((QofForeachTypeCB)g_free, NULL); } static struct From b9b7a9e0094ce89b09b648310da876e1133bb0d5 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sun, 22 Oct 2023 14:55:07 -0700 Subject: [PATCH 03/12] Add CI job for Asan build. --- .github/workflows/ci-tests.yml | 97 +++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 487cf64bea..7ebe090098 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -8,33 +8,70 @@ jobs: env: TZ: America/Los_Angeles steps: - - name: Checkout - uses: actions/checkout@v3 - - run: sudo apt-get update - - name: Install additional dependencies - run: sudo apt-get install -y gettext cmake libxslt-dev xsltproc ninja-build libboost-all-dev libgtk-3-dev guile-2.2-dev libgwengui-gtk3-dev libaqbanking-dev libofx-dev libdbi-dev libdbd-sqlite3 libwebkit2gtk-4.0-dev googletest - - name: Install language packs. - run: sudo apt-get --reinstall install -y language-pack-en language-pack-fr - - run: | - echo "ROOT_DIR=$GITHUB_WORKSPACE/.." >> $GITHUB_ENV - - name: Create Directories - run: | - pwd - mkdir $ROOT_DIR/inst - mkdir build - - name: Configure GnuCash - run: | - cd build - cmake -G Ninja -DWITH_PYTHON=ON -DCMAKE_INSTALL_PREFIX=$ROOT_DIR/inst $GITHUB_WORKSPACE - - name: Build and Test GnuCash - run: | - cd build - ninja - ninja distcheck - env: - CTEST_OUTPUT_ON_FAILURE: On - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: TestLog - path: ${{ github.workspace }}/build/Testing/Temporary/LastTest.log + - name: Checkout + uses: actions/checkout@v3 + - run: sudo apt-get update + - name: Install additional dependencies + run: sudo apt-get install -y gettext cmake libxslt-dev xsltproc ninja-build libboost-all-dev libgtk-3-dev guile-2.2-dev libgwengui-gtk3-dev libaqbanking-dev libofx-dev libdbi-dev libdbd-sqlite3 libwebkit2gtk-4.0-dev googletest + - name: Install language packs. + run: sudo apt-get --reinstall install -y language-pack-en language-pack-fr + - run: | + echo "ROOT_DIR=$GITHUB_WORKSPACE/.." >> $GITHUB_ENV + - name: Create Directories + run: | + pwd + mkdir $ROOT_DIR/inst + mkdir build + - name: Configure GnuCash + run: | + cd build + cmake -G Ninja -DWITH_PYTHON=ON -DCMAKE_INSTALL_PREFIX=$ROOT_DIR/inst $GITHUB_WORKSPACE + - name: Build and Test GnuCash + run: | + cd build + ninja + ninja distcheck + env: + CTEST_OUTPUT_ON_FAILURE: On + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: TestLog + path: ${{ github.workspace }}/build/Testing/Temporary/LastTest.log + ci_tests_ASAN: + runs-on: ubuntu-latest + name: Address Sanitizer CI Tests + continue-on-error: true + env: + TZ: America/Los_Angeles + steps: + - name: Checkout + uses: actions/checkout@v3 + - run: sudo apt-get update + - name: Install additional dependencies + run: sudo apt-get install -y gettext cmake libxslt-dev xsltproc ninja-build libboost-all-dev libgtk-3-dev guile-2.2-dev libgwengui-gtk3-dev libaqbanking-dev libofx-dev libdbi-dev libdbd-sqlite3 libwebkit2gtk-4.0-dev googletest + - name: Install language packs. + run: sudo apt-get --reinstall install -y language-pack-en language-pack-fr + - run: | + echo "ROOT_DIR=$GITHUB_WORKSPACE/.." >> $GITHUB_ENV + - name: Create Directories + run: | + pwd + mkdir $ROOT_DIR/inst + mkdir build + - name: Configure GnuCash + run: | + cd build + cmake -G Ninja -DWITH_PYTHON=ON -DCMAKE_INSTALL_PREFIX=$ROOT_DIR/inst $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Asan + - name: Build and Test GnuCash + run: | + cd build + ninja + ninja check + env: + CTEST_OUTPUT_ON_FAILURE: On + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: TestLog + path: ${{ github.workspace }}/build/Testing/Temporary/LastTest.log From fa119f8d17bb0f4daaa775209d8c4b913f96bdec Mon Sep 17 00:00:00 2001 From: John Ralls Date: Fri, 27 Oct 2023 14:07:57 -0700 Subject: [PATCH 04/12] Split test other --- libgnucash/engine/Split.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libgnucash/engine/Split.c b/libgnucash/engine/Split.c index 424ae16a9a..0272b020a5 100644 --- a/libgnucash/engine/Split.c +++ b/libgnucash/engine/Split.c @@ -725,7 +725,8 @@ xaccFreeSplit (Split *split) { Split *other = xaccSplitGetOtherSplit(split->gains_split); split->gains_split->gains_split = NULL; - other->gains_split = NULL; + if (other) + other->gains_split = NULL; } g_object_unref(split); From f67b53a44041be62acfa61b1ff7f1f2d4fc1b0d8 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Mon, 30 Oct 2023 14:03:50 -0700 Subject: [PATCH 05/12] Xml backend tests: Use libgnc-backend-xml-utils instead of sources. Every test was rebuilding it except for gnc-backend-xml.cpp from scratch, no point in that plus the Address Sanitizer needs the definition of GncBackendXml. --- libgnucash/backend/xml/test/CMakeLists.txt | 52 ++++------------------ 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/libgnucash/backend/xml/test/CMakeLists.txt b/libgnucash/backend/xml/test/CMakeLists.txt index 7b11268a6b..81b5e72ea5 100644 --- a/libgnucash/backend/xml/test/CMakeLists.txt +++ b/libgnucash/backend/xml/test/CMakeLists.txt @@ -26,40 +26,6 @@ function(add_xml_gtest _TARGET _SOURCE_FILES) target_compile_options(${_TARGET} PRIVATE -DU_SHOW_CPLUSPLUS_API=0 -DG_LOG_DOMAIN=\"gnc.backend.xml\") endfunction() -################################ - -set(test_backend_xml_base_SOURCES - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/sixtp-dom-parsers.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/sixtp-dom-generators.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/sixtp-utils.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/sixtp.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/sixtp-stack.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/sixtp-to-dom-parser.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-xml-helper.cpp -) - -## the xml backend is now a GModule - this test does -## not load it as a module and cannot link to it -## and remain portable. - -set(test_backend_xml_module_SOURCES - ${test_backend_xml_base_SOURCES} - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/io-example-account.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/io-gncxml-gen.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/io-gncxml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/io-utils.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-account-xml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-budget-xml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-lot-xml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-recurrence-xml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-schedxaction-xml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-freqspec-xml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-transaction-xml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-commodity-xml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-book-xml-v2.cpp - ${CMAKE_SOURCE_DIR}/libgnucash/backend/xml/gnc-pricedb-xml-v2.cpp -) - set_local_dist(test_backend_xml_DIST_local CMakeLists.txt grab-types.pl @@ -84,8 +50,8 @@ set_local_dist(test_backend_xml_DIST_local ) set(test_backend_xml_DIST ${test_backend_xml_DIST_local} ${test_backend_xml_test_files_DIST} PARENT_SCOPE) -add_xml_test(test-dom-converters1 "${test_backend_xml_base_SOURCES};test-dom-converters1.cpp") -add_xml_test(test-kvp-frames "${test_backend_xml_base_SOURCES};test-kvp-frames.cpp") +add_xml_test(test-dom-converters1 "test-dom-converters1.cpp") +add_xml_test(test-kvp-frames "test-kvp-frames.cpp") add_xml_test(test-load-backend test-load-backend.cpp) add_xml_test(test-load-xml2 test-load-xml2.cpp GNC_TEST_FILES=${CMAKE_CURRENT_SOURCE_DIR}/test-files/xml2 @@ -96,19 +62,19 @@ add_xml_test(test-load-xml2 test-load-xml2.cpp #) add_xml_test(test-load-example-account - "${test_backend_xml_module_SOURCES};test-load-example-account.cpp" + "test-load-example-account.cpp" GNC_ACCOUNT_PATH=${CMAKE_SOURCE_DIR}/data/accounts/C ) target_compile_options(test-load-example-account PRIVATE -DU_SHOW_CPLUSPLUS_API=0) add_xml_gtest(test-load-save-files gtest-load-save-files.cpp GNC_TEST_FILES=${CMAKE_CURRENT_SOURCE_DIR}/test-files/load-save ) -add_xml_test(test-string-converters "${test_backend_xml_base_SOURCES};test-string-converters.cpp") -add_xml_test(test-xml-account "${test_backend_xml_module_SOURCES};test-xml-account.cpp;test-file-stuff.cpp") -add_xml_test(test-xml-commodity "${test_backend_xml_module_SOURCES};test-xml-commodity.cpp;test-file-stuff.cpp") -add_xml_test(test-xml-pricedb "${test_backend_xml_module_SOURCES};test-xml-pricedb.cpp;test-file-stuff.cpp") -add_xml_test(test-xml-transaction "${test_backend_xml_module_SOURCES};test-xml-transaction.cpp;test-file-stuff.cpp") -add_xml_test(test-xml2-is-file "${test_backend_xml_module_SOURCES};test-xml2-is-file.cpp" +add_xml_test(test-string-converters "test-string-converters.cpp") +add_xml_test(test-xml-account "test-xml-account.cpp;test-file-stuff.cpp") +add_xml_test(test-xml-commodity "test-xml-commodity.cpp;test-file-stuff.cpp") +add_xml_test(test-xml-pricedb "test-xml-pricedb.cpp;test-file-stuff.cpp") +add_xml_test(test-xml-transaction "test-xml-transaction.cpp;test-file-stuff.cpp") +add_xml_test(test-xml2-is-file "test-xml2-is-file.cpp" GNC_TEST_FILES=${CMAKE_CURRENT_SOURCE_DIR}/test-files/xml2) set(test-real-data-env From 6940488d2df518d8fd6c168b0bd3bd088811c4c7 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Wed, 1 Nov 2023 21:49:48 +0800 Subject: [PATCH 06/12] recurrencePeriodTypeToString and recurrenceWeekendAdjustToString g_strdup only when necessary ... for sql use only. xml use assumes they return a const char*. --- libgnucash/backend/sql/gnc-recurrence-sql.cpp | 3 +-- libgnucash/engine/Recurrence.c | 8 ++++---- libgnucash/engine/Recurrence.h | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/libgnucash/backend/sql/gnc-recurrence-sql.cpp b/libgnucash/backend/sql/gnc-recurrence-sql.cpp index 967d5b81aa..feeb32d9af 100644 --- a/libgnucash/backend/sql/gnc-recurrence-sql.cpp +++ b/libgnucash/backend/sql/gnc-recurrence-sql.cpp @@ -379,14 +379,13 @@ upgrade_recurrence_table_1_2 (GncSqlBackend* sql_be) /* Step 2: insert a default value in the newly created column */ { - gchar* weekend_adj_str = recurrenceWeekendAdjustToString (WEEKEND_ADJ_NONE); + const gchar* weekend_adj_str = recurrenceWeekendAdjustToString (WEEKEND_ADJ_NONE); std::stringstream sql; sql << "UPDATE " << TABLE_NAME << " SET " << weekend_adjust_col_table[0]->name() << "='" << weekend_adj_str << "'"; auto stmt = sql_be->create_statement_from_sql(sql.str()); sql_be->execute_nonselect_statement(stmt); - g_free (weekend_adj_str); } /* Step 3: rewrite the table, requiring the weekend_adj column to be non-null */ diff --git a/libgnucash/engine/Recurrence.c b/libgnucash/engine/Recurrence.c index 5054b0ba1e..f507396acf 100644 --- a/libgnucash/engine/Recurrence.c +++ b/libgnucash/engine/Recurrence.c @@ -518,10 +518,10 @@ recurrenceListToString(const GList *r) return g_string_free(str, FALSE); } -gchar * +const gchar * recurrencePeriodTypeToString(PeriodType pt) { - return VALID_PERIOD_TYPE(pt) ? g_strdup(period_type_strings[pt]) : NULL; + return VALID_PERIOD_TYPE(pt) ? period_type_strings[pt] : NULL; } PeriodType @@ -535,10 +535,10 @@ recurrencePeriodTypeFromString(const gchar *str) return -1; } -gchar * +const gchar * recurrenceWeekendAdjustToString(WeekendAdjust wadj) { - return VALID_WEEKEND_ADJ(wadj) ? g_strdup(weekend_adj_strings[wadj]) : NULL; + return VALID_WEEKEND_ADJ(wadj) ? weekend_adj_strings[wadj] : NULL; } WeekendAdjust diff --git a/libgnucash/engine/Recurrence.h b/libgnucash/engine/Recurrence.h index 298cb6cbac..bcb92d86ed 100644 --- a/libgnucash/engine/Recurrence.h +++ b/libgnucash/engine/Recurrence.h @@ -155,9 +155,9 @@ void recurrenceListNextInstance(const GList *r, const GDate *refDate, GDate *nextDate); /* These four functions are only for xml storage, not user presentation. */ -gchar *recurrencePeriodTypeToString(PeriodType pt); +const gchar *recurrencePeriodTypeToString(PeriodType pt); PeriodType recurrencePeriodTypeFromString(const gchar *str); -gchar *recurrenceWeekendAdjustToString(WeekendAdjust wadj); +const gchar *recurrenceWeekendAdjustToString(WeekendAdjust wadj); WeekendAdjust recurrenceWeekendAdjustFromString(const gchar *str); /* For debugging. Caller owns the returned string. Not intl. */ From 087f135085b51aefed7464df291c4f37e862f229 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Thu, 9 Nov 2023 15:06:15 -0800 Subject: [PATCH 07/12] Add coverage option for Asan builds. --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c67221a98e..fb3f79e2a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ option (WITH_OFX "compile with ofx support (needs LibOFX)" ON) option (WITH_PYTHON "enable python plugin and bindings" OFF) option (ENABLE_BINRELOC "compile with binary relocation support" ON) option (DISABLE_NLS "do not use Native Language Support" OFF) +option (COVERAGE "Instrument an Asan build for coverage reporting" OFF) # ############################################################ # These are also settable from the command line in a similar way. @@ -623,6 +624,9 @@ elseif(UNIX) set(ASAN_DYNAMIC_LIB_ENV "LD_PRELOAD=${PRELOADS};ASAN_OPTIONS=${ASAN_OPTIONS}") endif () set(ASAN_BUILD_OPTIONS -fsanitize=address -fsanitize=undefined) +if (COVERAGE) + list(APPEND ASAN_BUILD_OPTIONS --coverage) +endif() set(ASAN_COMPILE_OPTIONS -g ${ASAN_BUILD_OPTIONS}) add_compile_options("$<$:${ASAN_COMPILE_OPTIONS}>") add_link_options("$<$:${ASAN_BUILD_OPTIONS}>") From dd0b72cdb5a6419dbcb64b49f5c4249a322e1f83 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Fri, 17 Nov 2023 15:20:29 -0800 Subject: [PATCH 08/12] Fix global array index out of range. Tests must set the account type to avoid errors in xaccAccountOrder. --- libgnucash/engine/test/gtest-import-map.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libgnucash/engine/test/gtest-import-map.cpp b/libgnucash/engine/test/gtest-import-map.cpp index 54980e6338..8ba2cffe0f 100644 --- a/libgnucash/engine/test/gtest-import-map.cpp +++ b/libgnucash/engine/test/gtest-import-map.cpp @@ -38,30 +38,37 @@ protected: t_asset_account1 = xaccMallocAccount(book); xaccAccountSetName(t_asset_account1, "Asset"); + xaccAccountSetType(t_asset_account1, ACCT_TYPE_ASSET); gnc_account_append_child(root, t_asset_account1); t_bank_account = xaccMallocAccount(book); xaccAccountSetName(t_bank_account, "Bank"); + xaccAccountSetType(t_bank_account, ACCT_TYPE_BANK); gnc_account_append_child(t_asset_account1, t_bank_account); t_asset_account2 = xaccMallocAccount(book); xaccAccountSetName(t_asset_account2, "Asset-Bank"); + xaccAccountSetType(t_asset_account2, ACCT_TYPE_ASSET); gnc_account_append_child(root, t_asset_account2); t_sav_account = xaccMallocAccount(book); xaccAccountSetName(t_sav_account, "Bank"); + xaccAccountSetType(t_sav_account,ACCT_TYPE_BANK); gnc_account_append_child(t_asset_account2, t_sav_account); t_expense_account = xaccMallocAccount(book); xaccAccountSetName(t_expense_account, "Expense"); + xaccAccountSetType(t_expense_account, ACCT_TYPE_EXPENSE); gnc_account_append_child(root, t_expense_account); t_expense_account1 = xaccMallocAccount(book); xaccAccountSetName(t_expense_account1, "Food"); + xaccAccountSetType(t_expense_account1, ACCT_TYPE_EXPENSE); gnc_account_append_child(t_expense_account, t_expense_account1); t_expense_account2 = xaccMallocAccount(book); xaccAccountSetName(t_expense_account2, "Drink"); + xaccAccountSetType(t_expense_account2, ACCT_TYPE_EXPENSE); gnc_account_append_child(t_expense_account, t_expense_account2); } void TearDown() { From a3f14759ab8c40e60c205166c43c7edb32bfbaa8 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sat, 25 Nov 2023 11:17:14 -0800 Subject: [PATCH 09/12] Asan: Make leak and ODR violation reporting CMake options. pass -DLEAKS=ON or -DODR=ON to enable these features. They have an effect only with CMAKE_BUILD_TYPE=Asan and don't work on Apple because Apple clang doesn't enable them. --- CMakeLists.txt | 32 +++++++++++++++---- bindings/python/CMakeLists.txt | 1 + bindings/python/tests/CMakeLists.txt | 2 +- .../cmake_modules/GncAddSchemeTargets.cmake | 2 +- common/cmake_modules/GncAddTest.cmake | 4 +-- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb3f79e2a0..8128186844 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,8 @@ option (WITH_PYTHON "enable python plugin and bindings" OFF) option (ENABLE_BINRELOC "compile with binary relocation support" ON) option (DISABLE_NLS "do not use Native Language Support" OFF) option (COVERAGE "Instrument an Asan build for coverage reporting" OFF) +option (LEAKS "Report leaks for tests in a non-Apple Asan build." OFF) +option (ODR "Report One Definition Rule violations in tests in a non-Apple Asan build." OFF) # ############################################################ # These are also settable from the command line in a similar way. @@ -616,20 +618,36 @@ if (APPLE) OUTPUT_VARIABLE ASAN_DYNAMIC_LIB OUTPUT_STRIP_TRAILING_WHITESPACE) set(ASAN_DYNAMIC_LIB_ENV "DYLD_INSERT_LIBRARIES=${ASAN_DYNAMIC_LIB}") + set(ASAN_BUILD_OPTIONS fast_unwind_on_malloc=0) elseif(UNIX) execute_process(COMMAND gcc -print-file-name=libasan.so OUTPUT_VARIABLE LIBASAN_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND gcc -print-file-name=libstdc++.so OUTPUT_VARIABLE LIBSTDCXX_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) - set(PRELOADS "${LIBASAN_PATH} ${LIBSTDCXX_PATH}") - set(ASAN_OPTIONS "detect_leaks=0:fast_unwind_on_malloc=0") - set(ASAN_DYNAMIC_LIB_ENV "LD_PRELOAD=${PRELOADS};ASAN_OPTIONS=${ASAN_OPTIONS}") + set(PRELOADS "${LIBASAN_PATH}:${LIBSTDCXX_PATH}") + set(ASAN_BUILD_OPTIONS "detect_leaks=0:fast_unwind_on_malloc=0") + set(ASAN_DYNAMIC_LIB_ENV LD_PRELOAD=${PRELOADS}) endif () -set(ASAN_BUILD_OPTIONS -fsanitize=address -fsanitize=undefined) +set(ASAN_LINK_OPTIONS -fsanitize=address -fsanitize=undefined) if (COVERAGE) - list(APPEND ASAN_BUILD_OPTIONS --coverage) + list(APPEND ASAN_LINK_OPTIONS --coverage) endif() -set(ASAN_COMPILE_OPTIONS -g ${ASAN_BUILD_OPTIONS}) +set(ASAN_COMPILE_OPTIONS -g ${ASAN_LINK_OPTIONS}) add_compile_options("$<$:${ASAN_COMPILE_OPTIONS}>") -add_link_options("$<$:${ASAN_BUILD_OPTIONS}>") +add_link_options("$<$:${ASAN_LINK_OPTIONS}>") +# See https://github.com/google/sanitizers/wiki/AddressSanitizerFlags#run-time-flags +set(ASAN_TEST_OPTIONS fast_unwind_on_malloc=0) +if (UNIX AND NOT APPLE) + if (LEAKS) + list(APPEND ASAN_TEST_OPTIONS detect_leaks=1) + else() + list(APPEND ASAN_TEST_OPTIONS detect_leaks=0) + endif() + if (ODR) + list(APPEND ASAN_TEST_OPTIONS detect_odr_violation=2) + else() + list(APPEND ASAN_TEST_OPTIONS detect_odr_violation=0) + endif() + string(REPLACE ";" ":" ASAN_TEST_OPTIONS "${ASAN_TEST_OPTIONS}") +endif() if (APPLE AND WITH_GNUCASH) set(CMAKE_MACOSX_RPATH ON) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 24a103a437..d37d047fd0 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -96,6 +96,7 @@ if(WITH_PYTHON) add_test(NAME sqlite3test COMMAND sqlite3test) add_dependencies(check sqlite3test) + set_tests_properties(sqlite3test PROPERTIES ENVIRONMENT "$<$:;ASAN_OPTIONS=${ASAN_TEST_OPTIONS}>") install(TARGETS gnucash_core_c LIBRARY DESTINATION ${PYTHON_SYSCONFIG_OUTPUT}/gnucash diff --git a/bindings/python/tests/CMakeLists.txt b/bindings/python/tests/CMakeLists.txt index 77733f8804..e68500d91d 100644 --- a/bindings/python/tests/CMakeLists.txt +++ b/bindings/python/tests/CMakeLists.txt @@ -9,7 +9,7 @@ if (WITH_PYTHON) add_dependencies(check test-python-bindings) add_test(NAME python-bindings COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/runTests.py.in) set(PYTHON_ENV "GNC_UNINSTALLED=1;GNC_BUILDDIR=${CMAKE_BINARY_DIR};PYTHONPATH=${PYTHON_SYSCONFIG_BUILD}:${LIBDIR_BUILD}/gnucash:${test_core_dir}") - set(ASAN_ENV "${ASAN_DYNAMIC_LIB_ENV};ASAN_OPTIONS=fast_unwind_on_malloc=0") + set(ASAN_ENV "${ASAN_DYNAMIC_LIB_ENV};ASAN_OPTIONS=${ASAN_TEST_OPTIONS}") set_tests_properties(python-bindings PROPERTIES ENVIRONMENT "$,${PYTHON_ENV};${ASAN_ENV},${PYTHON_ENV}>") endif() diff --git a/common/cmake_modules/GncAddSchemeTargets.cmake b/common/cmake_modules/GncAddSchemeTargets.cmake index bf86629b04..97c3c9d009 100644 --- a/common/cmake_modules/GncAddSchemeTargets.cmake +++ b/common/cmake_modules/GncAddSchemeTargets.cmake @@ -278,7 +278,7 @@ function(gnc_add_scheme_targets _TARGET) add_custom_command( OUTPUT ${output_file} COMMAND ${CMAKE_COMMAND} -E env - "${GUILE_ENV}$<$:;${ASAN_DYNAMIC_LIB_ENV}>" + "${GUILE_ENV}$<$:;${ASAN_DYNAMIC_LIB_ENV};ASAN_OPTIONS=${ASAN_BUILD_OPTIONS}>" ${GUILE_EXECUTABLE} -e "\(@@ \(guild\) main\)" -s ${GUILD_EXECUTABLE} compile -o ${output_file} ${source_file_abs_path} DEPENDS ${guile_depends} MAIN_DEPENDENCY ${source_file_abs_path} diff --git a/common/cmake_modules/GncAddTest.cmake b/common/cmake_modules/GncAddTest.cmake index bf40e3e758..09c04e0388 100644 --- a/common/cmake_modules/GncAddTest.cmake +++ b/common/cmake_modules/GncAddTest.cmake @@ -92,7 +92,7 @@ function(gnc_add_test _TARGET _SOURCE_FILES TEST_INCLUDE_VAR_NAME TEST_LIBS_VAR_ add_executable(${_TARGET} EXCLUDE_FROM_ALL ${_SOURCE_FILES}) target_link_libraries(${_TARGET} PRIVATE ${TEST_LIBS}) target_include_directories(${_TARGET} PRIVATE ${TEST_INCLUDE_DIRS}) - set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "$,${ENVVARS};ASAN_OPTIONS=fast_unwind_on_malloc=0,${ENVVARS}>") + set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "${ENVVARS}$<$:;ASAN_OPTIONS=${ASAN_TEST_OPTIONS}>") add_dependencies(check ${_TARGET}) endfunction() @@ -118,7 +118,7 @@ function(gnc_add_scheme_test _TARGET _SOURCE_FILE) (exit (run-test))" ) get_guile_env() - set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "$,${GUILE_ENV};${ASAN_DYNAMIC_LIB_ENV};ASAN_OPTIONS=fast_unwind_on_malloc=0;${ARGN},${GUILE_ENV};${ARGN}>") + set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "${GUILE_ENV}$<$:;${ASAN_DYNAMIC_LIB_ENV};ASAN_OPTIONS=${ASAN_TEST_OPTIONS}>;${ARGN}>") endfunction() function(gnc_add_scheme_tests _SOURCE_FILES) From 6834cb502501a330aca868f7a84dcb9272d0b90e Mon Sep 17 00:00:00 2001 From: John Ralls Date: Mon, 4 Dec 2023 10:43:50 -0800 Subject: [PATCH 10/12] Implement coverage option To use pass -DCMAKE_BUILD_TYPE=Debug or Asan -DCOVERAGE=ON and build as usual, then do ninja lcov-initialize && ninja check && ninja lcov-collect. The result will be a directory, /Coverage containing lcov tracefiles, including an aggregate file gnucash.info which you can use for further processing. It will also report an overall summary. Note that only C/C++ files are included. There's one more target, lcov-generate-html, that you can run after lcov-collect. It will generate a simple website in /Coverage-HTML showing coverage by source directory (the directories in have coverage for generated files). Each directory path is a clickable link to a page that shows coverage for each source file; the filenames link to a page for each showing which lines have been exercised. --- CMakeLists.txt | 14 ++- common/cmake_modules/GncCoverage.cmake | 111 ++++++++++++++++++ gnucash/CMakeLists.txt | 5 + gnucash/gnome-search/CMakeLists.txt | 4 + gnucash/gnome-utils/CMakeLists.txt | 4 + gnucash/gnome/CMakeLists.txt | 3 + gnucash/html/CMakeLists.txt | 5 + gnucash/import-export/CMakeLists.txt | 4 + gnucash/import-export/aqb/CMakeLists.txt | 4 + .../import-export/bi-import/CMakeLists.txt | 4 + gnucash/import-export/csv-exp/CMakeLists.txt | 4 + gnucash/import-export/csv-imp/CMakeLists.txt | 4 + .../customer-import/CMakeLists.txt | 4 + .../import-export/log-replay/CMakeLists.txt | 4 + gnucash/import-export/ofx/CMakeLists.txt | 4 + gnucash/import-export/qif-imp/CMakeLists.txt | 4 + gnucash/register/ledger-core/CMakeLists.txt | 4 + gnucash/register/register-core/CMakeLists.txt | 4 + .../register/register-gnome/CMakeLists.txt | 4 + gnucash/report/CMakeLists.txt | 4 + libgnucash/app-utils/CMakeLists.txt | 6 +- libgnucash/backend/dbi/CMakeLists.txt | 4 + libgnucash/backend/sql/CMakeLists.txt | 4 + libgnucash/backend/xml/CMakeLists.txt | 8 ++ libgnucash/core-utils/CMakeLists.txt | 4 + libgnucash/engine/CMakeLists.txt | 4 + libgnucash/gnc-module/CMakeLists.txt | 4 + libgnucash/tax/CMakeLists.txt | 4 + 28 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 common/cmake_modules/GncCoverage.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8128186844..7660a7f55e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ option (WITH_OFX "compile with ofx support (needs LibOFX)" ON) option (WITH_PYTHON "enable python plugin and bindings" OFF) option (ENABLE_BINRELOC "compile with binary relocation support" ON) option (DISABLE_NLS "do not use Native Language Support" OFF) -option (COVERAGE "Instrument an Asan build for coverage reporting" OFF) +option (COVERAGE "Instrument a Debug or Asan build for coverage reporting" OFF) option (LEAKS "Report leaks for tests in a non-Apple Asan build." OFF) option (ODR "Report One Definition Rule violations in tests in a non-Apple Asan build." OFF) # ############################################################ @@ -628,7 +628,13 @@ elseif(UNIX) endif () set(ASAN_LINK_OPTIONS -fsanitize=address -fsanitize=undefined) if (COVERAGE) - list(APPEND ASAN_LINK_OPTIONS --coverage) + include(GncCoverage) +endif() +if (COVERAGE) + set(COVERAGE_COMPILE_OPTION --coverage) + add_compile_options("$<$:${COVERAGE_COMPILE_OPTION}>") + add_link_options("$<$:${COVERAGE_COMPILE_OPTION}>") + list(APPEND ASAN_LINK_OPTIONS ${COVERAGE_COMPILE_OPTION}) endif() set(ASAN_COMPILE_OPTIONS -g ${ASAN_LINK_OPTIONS}) add_compile_options("$<$:${ASAN_COMPILE_OPTIONS}>") @@ -689,6 +695,10 @@ add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} ) +if (COVERAGE) + add_dependencies(check lcov-initialize) +endif() + set(gnucash_DOCS AUTHORS ChangeLog.1999 diff --git a/common/cmake_modules/GncCoverage.cmake b/common/cmake_modules/GncCoverage.cmake new file mode 100644 index 0000000000..79dde288a0 --- /dev/null +++ b/common/cmake_modules/GncCoverage.cmake @@ -0,0 +1,111 @@ +#lcov command options: +# -a, --add-tracefile (takes glob) +# -c, --capture; create a trace file from the .da files +# -e, --extract; a reduced scope for analysis +# -l, --list; the contents of a tracefile +# -r, --remove; remove pattern-match from tracefile +# -z, --zerocounters; run this first, then -c -i +# --diff +# --summary +# other necessary options: +# --directory; points to the source root. If left off it analyzes the kernel +# --no-external; only analyze code in --directory +# --build-directory; where the .no when different from the .da files +# --branch-coverage; to ensure branch info is saved +# --demangle-cpp; requires c++filt + +if (COVERAGE) + find_program(LCOV lcov) + find_program(GENINFO geninfo) + find_program(GENHTML genhtml) + find_program(CPPFILT c++filt) + + if (NOT LCOV OR NOT GENINFO OR NOT GENHTML OR NOT CPPFILT) + MESSAGE(WARNING "A required program for presenting coverage information isn't available, disabling coverage") + set(COVERAGE OFF CACHE INTERNAL "") + return() + endif() +else() + return() +endif() + +execute_process(COMMAND lcov --version OUTPUT_VARIABLE lcov_version_response) +string(REGEX MATCH "[-.0-9]+" lcov_version ${lcov_version_response}) +set(LCOV_VERSION ${lcov_version} CACHE INTERNAL "") + +set(excludes_arg "") +foreach (sys_path ${CMAKE_SYSTEM_PREFIX_PATH}) + list(APPEND excludes_arg "--exclude" "${sys_path}/*") +endforeach() + +set(ignores_arg "--ignore-errors" "unused,unused" "--ignore-errors" "mismatch,mismatch" + "--ignore-errors" "empty,empty") +list(APPEND ignores_arg "--rc" "geninfo_unexecuted_blocks=1") +set(generate_flags "") +set(geninfo_flags --quiet ${excludes_arg}) +if (LCOV_VERSION VERSION_GREATER_EQUAL "2.0") + list(APPEND geninfo_flags ${ignores_arg}) + list(APPEND generate_flags "--branch-coverage" "--demangle-cpp" "c++filt") +else() + list(APPEND generate_flags "--rc" "lcov_branch_coverage=1" "--rc" "genhtml_demangle_cpp=1") +endif() +set(coverage_dir "${CMAKE_BINARY_DIR}/Coverage" CACHE INTERNAL "Directory to accumulate coverage tracefiles") +set(coverage_html_dir "${CMAKE_BINARY_DIR}/Coverage-HTML" CACHE INTERNAL "Directory to place HTML coverage results pages") + +file(MAKE_DIRECTORY ${coverage_dir}) + +add_custom_target(lcov-initialize) +if (LCOV_VERSION VERSION_GREATER_EQUAL "2.0") + add_custom_target(lcov-collect + COMMAND lcov ${geninfo_flags} -a ${coverage_dir}/*.info -o ${coverage_dir}/gnucash.info + COMMAND lcov --summary ${coverage_dir}/gnucash.info + VERBATIM + COMMAND_EXPAND_LISTS) +else() + file(GENERATE OUTPUT ${CMAKE_BINARY_DIR}/collect.sh + CONTENT + "#!/bin/bash +if [ -e $2 ] + then rm $2 +fi +j=\"\" +for i in $1/*.info +do j=\"$j -a $i\" +done +lcov $j -o $2 +" + FILE_PERMISSIONS OWNER_EXECUTE OWNER_READ OWNER_WRITE WORLD_EXECUTE) + + add_custom_target(lcov-collect + COMMAND ${CMAKE_COMMAND} -E env ${CMAKE_BINARY_DIR}/collect.sh ${coverage_dir} ${coverage_dir}/gnucash.info + DEPENDS ${CMAKE_BINARY_DIR}/collect.sh + VERBATIM + COMMAND_EXPAND_LISTS) +endif() +set_target_properties(lcov-collect PROPERTIES ADDITIONAL_CLEAN_FILES "${coverage_dir}/gnucash.info") + +add_custom_target(lcov-generate-html + genhtml --quiet --output-directory "${coverage_html_dir}" --legend --function-coverage ${generate_flags} ${coverage_dir}/gnucash.info + VERBATIM + COMMAND_EXPAND_LISTS) +set_target_properties(lcov-generate-html PROPERTIES ADDITIONAL_CLEAN_FILES "${coverage_html_dir}") + +function (add_coverage_target tgt) + get_target_property(build_dir ${tgt} BINARY_DIR) + set(target_dir "${build_dir}/CMakeFiles/${tgt}.dir") + + add_custom_target(lcov-initialize-${tgt} + lcov ${geninfo_flags} -z --directory ${target_dir} + COMMAND lcov ${geninfo_flags} ${generate_flags} -c -i --directory ${target_dir} -o "${coverage_dir}/${tgt}_base.info" + VERBATIM + COMMAND_EXPAND_LISTS) + add_dependencies(lcov-initialize lcov-initialize-${tgt}) + add_dependencies(lcov-initialize-${tgt} ${tgt}) + + add_custom_target(lcov-collect-${tgt} + COMMAND lcov ${geninfo_flags} ${generate_flags} -c --directory ${target_dir} -o "${coverage_dir}/${tgt}_result.info" + VERBATIM + COMMAND_EXPAND_LISTS) + add_dependencies(lcov-collect lcov-collect-${tgt}) + set_target_properties(${tgt} PROPERTIES ADDITIONAL_CLEAN_FILES "${coverage_dir}/${tgt}_base.info;${coverage_dir}/${tgt}_result.info") +endFunction() diff --git a/gnucash/CMakeLists.txt b/gnucash/CMakeLists.txt index a9461979b3..7bc6fa91e1 100644 --- a/gnucash/CMakeLists.txt +++ b/gnucash/CMakeLists.txt @@ -160,6 +160,11 @@ if (MAC_INTEGRATION) target_link_options(gnucash-cli PRIVATE -Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_SOURCE_DIR}/Info.plist) endif() +if (COVERAGE AND LCOV_VERSION VERSION_GREATER_EQUAL "2.0") + add_coverage_target(gnucash) + add_coverage_target(gnucash-cli) +endif() + install(TARGETS gnucash gnucash-cli DESTINATION ${CMAKE_INSTALL_BINDIR}) # No headers to install. diff --git a/gnucash/gnome-search/CMakeLists.txt b/gnucash/gnome-search/CMakeLists.txt index 55c9ab3241..e945bf1665 100644 --- a/gnucash/gnome-search/CMakeLists.txt +++ b/gnucash/gnome-search/CMakeLists.txt @@ -55,6 +55,10 @@ if (APPLE) set_target_properties (gnc-gnome-search PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (COVERAGE) + add_coverage_target(gnc-gnome-search) +endif() + install(TARGETS gnc-gnome-search LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/gnome-utils/CMakeLists.txt b/gnucash/gnome-utils/CMakeLists.txt index e02b68795e..8040cade84 100644 --- a/gnucash/gnome-utils/CMakeLists.txt +++ b/gnucash/gnome-utils/CMakeLists.txt @@ -224,6 +224,10 @@ if (MAC_INTEGRATION) target_link_libraries(gnc-gnome-utils ${OSX_EXTRA_LIBRARIES}) endif() +if (COVERAGE) + add_coverage_target(gnc-gnome-utils) +endif() + target_include_directories(gnc-gnome-utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/gnucash/gnome/CMakeLists.txt b/gnucash/gnome/CMakeLists.txt index e0c7dc9320..68006a01d9 100644 --- a/gnucash/gnome/CMakeLists.txt +++ b/gnucash/gnome/CMakeLists.txt @@ -163,6 +163,9 @@ if (MAC_INTEGRATION) target_link_libraries(gnc-gnome ${OSX_EXTRA_LIBRARIES}) endif() +if (COVERAGE) + add_coverage_target(gnc-gnome) +endif() install(TARGETS gnc-gnome LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/gnucash/html/CMakeLists.txt b/gnucash/html/CMakeLists.txt index acd8ea0b5d..1efdaf0133 100644 --- a/gnucash/html/CMakeLists.txt +++ b/gnucash/html/CMakeLists.txt @@ -73,6 +73,11 @@ if (APPLE) endif() add_dependencies(gnc-html swig-gnc-html-c) + +if (COVERAGE) + add_coverage_target(gnc-html) +endif() + install(TARGETS gnc-html LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/import-export/CMakeLists.txt b/gnucash/import-export/CMakeLists.txt index 5cdde066f4..3765468acd 100644 --- a/gnucash/import-export/CMakeLists.txt +++ b/gnucash/import-export/CMakeLists.txt @@ -61,6 +61,10 @@ if (APPLE) set_target_properties (gnc-generic-import PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}") endif() +if (COVERAGE) + add_coverage_target(gnc-generic-import) +endif() + install(TARGETS gnc-generic-import LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/gnucash/import-export/aqb/CMakeLists.txt b/gnucash/import-export/aqb/CMakeLists.txt index cf90236227..662638a849 100644 --- a/gnucash/import-export/aqb/CMakeLists.txt +++ b/gnucash/import-export/aqb/CMakeLists.txt @@ -81,6 +81,10 @@ if(WITH_AQBANKING) set_target_properties (gncmod-aqbanking PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() + if (COVERAGE) + add_coverage_target(gncmod-aqbanking) + endif() + install(TARGETS gncmod-aqbanking LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/import-export/bi-import/CMakeLists.txt b/gnucash/import-export/bi-import/CMakeLists.txt index 53b0c2ed52..54839a65bb 100644 --- a/gnucash/import-export/bi-import/CMakeLists.txt +++ b/gnucash/import-export/bi-import/CMakeLists.txt @@ -37,6 +37,10 @@ if (APPLE) set_target_properties (gnc-bi-import PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (COVERAGE AND LCOV_VERSION VERSION_GREATER_EQUAL "2.0") + add_coverage_target(gnc-bi-import) +endif() + install(TARGETS gnc-bi-import LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/import-export/csv-exp/CMakeLists.txt b/gnucash/import-export/csv-exp/CMakeLists.txt index d413a74569..5ff28385d7 100644 --- a/gnucash/import-export/csv-exp/CMakeLists.txt +++ b/gnucash/import-export/csv-exp/CMakeLists.txt @@ -43,6 +43,10 @@ if (APPLE) set_target_properties (gnc-csv-export PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (COVERAGE) + add_coverage_target(gnc-csv-export) +endif() + install(TARGETS gnc-csv-export LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/import-export/csv-imp/CMakeLists.txt b/gnucash/import-export/csv-imp/CMakeLists.txt index 5347cfdec2..b164c7cac6 100644 --- a/gnucash/import-export/csv-imp/CMakeLists.txt +++ b/gnucash/import-export/csv-imp/CMakeLists.txt @@ -84,6 +84,10 @@ if (APPLE) set_target_properties (gnc-csv-import PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (COVERAGE) + add_coverage_target(gnc-csv-import) +endif() + install(TARGETS gnc-csv-import LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/import-export/customer-import/CMakeLists.txt b/gnucash/import-export/customer-import/CMakeLists.txt index 126f5b6dd7..f0b2892c30 100644 --- a/gnucash/import-export/customer-import/CMakeLists.txt +++ b/gnucash/import-export/customer-import/CMakeLists.txt @@ -35,6 +35,10 @@ if (APPLE) set_target_properties (gnc-customer-import PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (0 AND COVERAGE) # Generates no coverage data + add_coverage_target(gnc-customer-import) +endif() + install(TARGETS gnc-customer-import LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/import-export/log-replay/CMakeLists.txt b/gnucash/import-export/log-replay/CMakeLists.txt index 742c3dc7b5..c25ab751e9 100644 --- a/gnucash/import-export/log-replay/CMakeLists.txt +++ b/gnucash/import-export/log-replay/CMakeLists.txt @@ -32,6 +32,10 @@ if (APPLE) set_target_properties (gnc-log-replay PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (COVERAGE AND LCOV_VERSION VERSION_GREATER_EQUAL "2.0") + add_coverage_target(gnc-log-replay) +endif() + install(TARGETS gnc-log-replay LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/import-export/ofx/CMakeLists.txt b/gnucash/import-export/ofx/CMakeLists.txt index 479e16e24d..f5dace314a 100644 --- a/gnucash/import-export/ofx/CMakeLists.txt +++ b/gnucash/import-export/ofx/CMakeLists.txt @@ -48,6 +48,10 @@ if (WITH_OFX) set_target_properties (gncmod-ofx PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() + if (COVERAGE AND LCOV_VERSION VERSION_GREATER_EQUAL "2.0") + add_coverage_target(gncmod-ofx) + endif() + install(TARGETS gncmod-ofx LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/import-export/qif-imp/CMakeLists.txt b/gnucash/import-export/qif-imp/CMakeLists.txt index 8d7223233d..ec4a6fa52f 100644 --- a/gnucash/import-export/qif-imp/CMakeLists.txt +++ b/gnucash/import-export/qif-imp/CMakeLists.txt @@ -36,6 +36,10 @@ if (APPLE) set_target_properties (gnc-qif-import PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (COVERAGE AND LCOV_VERSION VERSION_GREATER_EQUAL "2.0") + add_coverage_target(gnc-qif-import) +endif() + install(TARGETS gnc-qif-import LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/register/ledger-core/CMakeLists.txt b/gnucash/register/ledger-core/CMakeLists.txt index 3bfa5b0df1..19563f3a58 100644 --- a/gnucash/register/ledger-core/CMakeLists.txt +++ b/gnucash/register/ledger-core/CMakeLists.txt @@ -56,6 +56,10 @@ if (APPLE) set_target_properties (gnc-ledger-core PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (COVERAGE) + add_coverage_target(gnc-ledger-core) +endif() + install(TARGETS gnc-ledger-core LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/register/register-core/CMakeLists.txt b/gnucash/register/register-core/CMakeLists.txt index 8c1afeee44..a463a34244 100644 --- a/gnucash/register/register-core/CMakeLists.txt +++ b/gnucash/register/register-core/CMakeLists.txt @@ -60,6 +60,10 @@ install(TARGETS gnc-register-core ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +if (COVERAGE) + add_coverage_target(gnc-register-core) +endif() + install(FILES ${register_core_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/gnucash) set_local_dist(register_core_DIST_local CMakeLists.txt README ${register_core_SOURCES} ${register_core_HEADERS}) diff --git a/gnucash/register/register-gnome/CMakeLists.txt b/gnucash/register/register-gnome/CMakeLists.txt index 6d893fe2aa..2960f85dfa 100644 --- a/gnucash/register/register-gnome/CMakeLists.txt +++ b/gnucash/register/register-gnome/CMakeLists.txt @@ -55,6 +55,10 @@ if (APPLE) set_target_properties (gnc-register-gnome PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (COVERAGE) + add_coverage_target(gnc-register-gnome) +endif() + install(TARGETS gnc-register-gnome LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/gnucash/report/CMakeLists.txt b/gnucash/report/CMakeLists.txt index 25331a1d88..c70e13c561 100644 --- a/gnucash/report/CMakeLists.txt +++ b/gnucash/report/CMakeLists.txt @@ -43,6 +43,10 @@ if (APPLE) set_target_properties (gnc-report PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() +if (COVERAGE) + add_coverage_target(gnc-report) +endif() + install(TARGETS gnc-report LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/libgnucash/app-utils/CMakeLists.txt b/libgnucash/app-utils/CMakeLists.txt index a9ca0acad1..b3b0efd421 100644 --- a/libgnucash/app-utils/CMakeLists.txt +++ b/libgnucash/app-utils/CMakeLists.txt @@ -84,7 +84,11 @@ if (APPLE) set_target_properties (gnc-app-utils PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}") endif() -install(TARGETS gnc-app-utils +if (COVERAGE) + add_coverage_target(gnc-app-utils) +endif() + +install (TARGETS gnc-app-utils LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/libgnucash/backend/dbi/CMakeLists.txt b/libgnucash/backend/dbi/CMakeLists.txt index 9db2ebbe22..ac6fe7058d 100644 --- a/libgnucash/backend/dbi/CMakeLists.txt +++ b/libgnucash/backend/dbi/CMakeLists.txt @@ -43,6 +43,10 @@ if (WITH_SQL) set_target_properties (gncmod-backend-dbi PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash") endif() + if (COVERAGE) + add_coverage_target(gncmod-backend-dbi) + endif() + install(TARGETS gncmod-backend-dbi LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash diff --git a/libgnucash/backend/sql/CMakeLists.txt b/libgnucash/backend/sql/CMakeLists.txt index 3fc3fa3d03..b197c39a1d 100644 --- a/libgnucash/backend/sql/CMakeLists.txt +++ b/libgnucash/backend/sql/CMakeLists.txt @@ -80,5 +80,9 @@ if(WITH_SQL) ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + if (COVERAGE) + add_coverage_target(gnc-backend-sql) + endif() + # No headers to install endif() diff --git a/libgnucash/backend/xml/CMakeLists.txt b/libgnucash/backend/xml/CMakeLists.txt index dbd6a25fa8..b21cf5d733 100644 --- a/libgnucash/backend/xml/CMakeLists.txt +++ b/libgnucash/backend/xml/CMakeLists.txt @@ -96,6 +96,10 @@ install(TARGETS gnc-backend-xml-utils ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) # No headers to install +if (COVERAGE) + add_coverage_target(gnc-backend-xml-utils) +endif() + # ---- @@ -119,6 +123,10 @@ if (APPLE) set_target_properties (gncmod-backend-xml PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}") endif() +if (COVERAGE) + add_coverage_target(gncmod-backend-xml) +endif() + install(TARGETS gncmod-backend-xml LIBRARY DESTINATION ${LIB_DIR} ARCHIVE DESTINATION ${LIB_DIR} diff --git a/libgnucash/core-utils/CMakeLists.txt b/libgnucash/core-utils/CMakeLists.txt index 2c77d45a02..773eb875dc 100644 --- a/libgnucash/core-utils/CMakeLists.txt +++ b/libgnucash/core-utils/CMakeLists.txt @@ -72,6 +72,10 @@ install(TARGETS gnc-core-utils RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) +if (COVERAGE) + add_coverage_target(gnc-core-utils) +endif() + ### gncla-dir.h set(prefix ${CMAKE_INSTALL_PREFIX}) diff --git a/libgnucash/engine/CMakeLists.txt b/libgnucash/engine/CMakeLists.txt index cb78372b8b..6fc7f8b118 100644 --- a/libgnucash/engine/CMakeLists.txt +++ b/libgnucash/engine/CMakeLists.txt @@ -263,6 +263,10 @@ if (APPLE) set_target_properties (gnc-engine PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}") endif() +if (COVERAGE) + add_coverage_target(gnc-engine) +endif() + install(TARGETS gnc-engine LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/libgnucash/gnc-module/CMakeLists.txt b/libgnucash/gnc-module/CMakeLists.txt index b686d24725..80c997cea5 100644 --- a/libgnucash/gnc-module/CMakeLists.txt +++ b/libgnucash/gnc-module/CMakeLists.txt @@ -27,6 +27,10 @@ target_include_directories (gnc-module ${CMAKE_BINARY_DIR}/common # for config.h ) +if (COVERAGE) + add_coverage_target(gnc-module) +endif() + install(TARGETS gnc-module LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/libgnucash/tax/CMakeLists.txt b/libgnucash/tax/CMakeLists.txt index 945bc7b8c5..fef9682dd6 100644 --- a/libgnucash/tax/CMakeLists.txt +++ b/libgnucash/tax/CMakeLists.txt @@ -23,6 +23,10 @@ if (APPLE) set_target_properties (gnc-locale-tax PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}") endif() +if (COVERAGE) + add_coverage_target(gnc-locale-tax) +endif() + install(TARGETS gnc-locale-tax LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} From 1e85d0b1158ecfd186fa898f5878efe60f0df708 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Mon, 4 Dec 2023 11:34:10 -0800 Subject: [PATCH 11/12] Add workflow job to generate a coverage report. URI is https://gnucash.github.io/ --- .github/workflows/coverage.yml | 66 ++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000000..3fd09aba6f --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,66 @@ +name: coverage +on: push +permissions: {} +jobs: + coverage: + runs-on: ubuntu-latest + name: C++ Code coverage analysis + continue-on-error: true + env: + TZ: America/Los_Angeles + steps: + - name: Checkout + uses: actions/checkout@v3 + - run: sudo apt-get update + - name: Install additional dependencies + run: sudo apt-get install -y gettext cmake libxslt-dev xsltproc ninja-build libboost-all-dev libgtk-3-dev guile-2.2-dev libgwengui-gtk3-dev libaqbanking-dev libofx-dev libdbi-dev libdbd-sqlite3 libwebkit2gtk-4.0-dev googletest lcov + - name: Install language packs. + run: sudo apt-get --reinstall install -y language-pack-en language-pack-fr + - run: | + echo "ROOT_DIR=$GITHUB_WORKSPACE/.." >> $GITHUB_ENV + - name: Create Directories + run: | + pwd + mkdir $ROOT_DIR/inst + mkdir build + - name: Configure GnuCash + run: | + cd build + cmake -G Ninja -DWITH_PYTHON=ON -DCMAKE_INSTALL_PREFIX=$ROOT_DIR/inst $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DCOVERAGE=ON + - name: Build and test GnuCash with coverage analysis + run: | + cd build + ninja + ninja lcov-initialize + ninja check + ninja -k 0 lcov-collect + ninja lcov-generate-html + env: + CTEST_OUTPUT_ON_FAILURE: Off + - name: prepare_upload + if: success() + run: | + mkdir build/github-pages + mv build/Coverage-HTML build/github-pages/ + chmod -v -R +rX "build/github-pages" | while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done + - name: Upload pages + uses: actions/upload-pages-artifact@v2 + if: success() + with: + path: ${{ github.workspace }}/build/github-pages + + deploy-coverage: + needs: coverage + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Publish + id: deployment + uses: actions/deploy-pages@v2 From d92d97aef618679b1f0309d648c507d5913c63d6 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Tue, 5 Dec 2023 17:00:52 -0800 Subject: [PATCH 12/12] Add Scheme coverage collection with option GUILE_COVERAGE. This can be used with or without COVERAGE, though if without the results will reflect only the Scheme code exercised by the tests. --- CMakeLists.txt | 3 +- bindings/guile/test/srfi64-extras.scm | 2 +- common/cmake_modules/GncAddTest.cmake | 53 +++++++++++++++++++++----- common/cmake_modules/GncCoverage.cmake | 2 +- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7660a7f55e..4ec3c712a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ option (WITH_PYTHON "enable python plugin and bindings" OFF) option (ENABLE_BINRELOC "compile with binary relocation support" ON) option (DISABLE_NLS "do not use Native Language Support" OFF) option (COVERAGE "Instrument a Debug or Asan build for coverage reporting" OFF) +option (GUILE_COVERAGE "Compute testing coverage of Scheme code. WARNING: 15X slowdown!" OFF) option (LEAKS "Report leaks for tests in a non-Apple Asan build." OFF) option (ODR "Report One Definition Rule violations in tests in a non-Apple Asan build." OFF) # ############################################################ @@ -627,7 +628,7 @@ elseif(UNIX) set(ASAN_DYNAMIC_LIB_ENV LD_PRELOAD=${PRELOADS}) endif () set(ASAN_LINK_OPTIONS -fsanitize=address -fsanitize=undefined) -if (COVERAGE) +if (COVERAGE OR GUILE_COVERAGE) include(GncCoverage) endif() if (COVERAGE) diff --git a/bindings/guile/test/srfi64-extras.scm b/bindings/guile/test/srfi64-extras.scm index e572e988a8..b41baee432 100644 --- a/bindings/guile/test/srfi64-extras.scm +++ b/bindings/guile/test/srfi64-extras.scm @@ -45,5 +45,5 @@ (lambda (runner) (format #t "Source:~a\npass = ~a, fail = ~a\n" (test-result-ref runner 'source-file) num-passed num-failed) - (exit (zero? num-failed)))) + (zero? num-failed))) runner)) diff --git a/common/cmake_modules/GncAddTest.cmake b/common/cmake_modules/GncAddTest.cmake index 09c04e0388..ef3c96aa45 100644 --- a/common/cmake_modules/GncAddTest.cmake +++ b/common/cmake_modules/GncAddTest.cmake @@ -34,6 +34,15 @@ function(get_guile_env) set(guile_load_paths "") list(APPEND guile_load_paths "${CMAKE_BINARY_DIR}/${GUILE_REL_SITEDIR}") list(APPEND guile_load_paths "${CMAKE_BINARY_DIR}/${GUILE_REL_SITEDIR}/gnucash/deprecated") # Path to gnucash' deprecated modules + if (GUILE_COVERAGE) + list(APPEND guile_load_paths "${CMAKE_BINARY_DIR}/${GUILE_REL_SITEDIR}/gnucash") + list(APPEND guile_load_paths "${CMAKE_BINARY_DIR}/${GUILE_REL_SITEDIR}/gnucash/report") + list(APPEND guile_load_paths "${CMAKE_BINARY_DIR}/${GUILE_REL_SITEDIR}/gnucash/reports") + list(APPEND guile_load_paths "${CMAKE_BINARY_DIR}/${GUILE_REL_SITEDIR}/gnucash/engine") + list(APPEND guile_load_paths "${CMAKE_BINARY_DIR}/${GUILE_REL_SITEDIR}/gnucash/app-utils") + list(APPEND guile_load_paths "${CMAKE_BINARY_DIR}/${GUILE_REL_SITEDIR}/gnucash/qif-import") + + endif() set(guile_load_path "${guile_load_paths}") set(guile_load_compiled_paths "") @@ -103,20 +112,46 @@ function(gnc_add_test_with_guile _TARGET _SOURCE_FILES TEST_INCLUDE_VAR_NAME TES ) endfunction() - function(gnc_add_scheme_test _TARGET _SOURCE_FILE) - add_test(NAME ${_TARGET} COMMAND ${GUILE_EXECUTABLE} --debug -c " - (set! %load-hook + if (GUILE_COVERAGE) + add_test(NAME ${_TARGET} COMMAND ${GUILE_EXECUTABLE} --debug -c " + (set! %load-hook (lambda (filename) - (when (and filename - (string-contains filename \"${GUILE_REL_SITEDIR}\") - (not (string-prefix? \"${CMAKE_BINARY_DIR}\" filename))) + (when (and filename + (string-contains filename \"${GUILE_REL_SITEDIR}\") + (not (string-prefix? \"${CMAKE_BINARY_DIR}\" filename))) (format #t \"%load-path = ~s~%\" %load-path) (format #t \"%load-compiled-path = ~s~%\" %load-compiled-path) (error \"Loading guile/site file from outside build tree!\" filename)))) - (load-from-path \"${_TARGET}\") - (exit (run-test))" - ) + (load-from-path \"${_TARGET}\") + (use-modules (system vm coverage) + (system vm vm)) + (call-with-values (lambda () + (with-code-coverage + (lambda () + (run-test)))) + + (lambda (data result) + (let ((port (open-output-file \"${coverage_dir}/${_TARGET}_results.info\"))) + (coverage-data->lcov data port) + (close port)) + (exit result))) +" + ) + else() + add_test(NAME ${_TARGET} COMMAND ${GUILE_EXECUTABLE} --debug -c " + (set! %load-hook + (lambda (filename) + (when (and filename + (string-contains filename \"${GUILE_REL_SITEDIR}\") + (not (string-prefix? \"${CMAKE_BINARY_DIR}\" filename))) + (format #t \"%load-path = ~s~%\" %load-path) + (format #t \"%load-compiled-path = ~s~%\" %load-compiled-path) + (error \"Loading guile/site file from outside build tree!\" filename)))) + (load-from-path \"${_TARGET}\") + (exit (run-test))" + ) + endif() get_guile_env() set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "${GUILE_ENV}$<$:;${ASAN_DYNAMIC_LIB_ENV};ASAN_OPTIONS=${ASAN_TEST_OPTIONS}>;${ARGN}>") endfunction() diff --git a/common/cmake_modules/GncCoverage.cmake b/common/cmake_modules/GncCoverage.cmake index 79dde288a0..64843d1f71 100644 --- a/common/cmake_modules/GncCoverage.cmake +++ b/common/cmake_modules/GncCoverage.cmake @@ -14,7 +14,7 @@ # --branch-coverage; to ensure branch info is saved # --demangle-cpp; requires c++filt -if (COVERAGE) +if (COVERAGE OR GUILE_COVERAGE) find_program(LCOV lcov) find_program(GENINFO geninfo) find_program(GENHTML genhtml)