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 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index dd9148a523..4ec3c712a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,10 @@ 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 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) # ############################################################ # These are also settable from the command line in a similar way. @@ -610,6 +614,48 @@ 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}") + 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_BUILD_OPTIONS "detect_leaks=0:fast_unwind_on_malloc=0") + set(ASAN_DYNAMIC_LIB_ENV LD_PRELOAD=${PRELOADS}) +endif () +set(ASAN_LINK_OPTIONS -fsanitize=address -fsanitize=undefined) +if (COVERAGE OR GUILE_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}>") +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) set(CMAKE_INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}") @@ -650,6 +696,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/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/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/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 9ec9ca29a9..e68500d91d 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=${ASAN_TEST_OPTIONS}") + 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..97c3c9d009 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};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} + COMMAND_EXPAND_LISTS VERBATIM ) endforeach(source_file) diff --git a/common/cmake_modules/GncAddTest.cmake b/common/cmake_modules/GncAddTest.cmake index 6d095afc4b..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 "") @@ -77,49 +86,74 @@ 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=${ASAN_TEST_OPTIONS}>") 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 " - (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};${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) diff --git a/common/cmake_modules/GncCoverage.cmake b/common/cmake_modules/GncCoverage.cmake new file mode 100644 index 0000000000..64843d1f71 --- /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 OR GUILE_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/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/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/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/backend/xml/test/CMakeLists.txt b/libgnucash/backend/xml/test/CMakeLists.txt index 58b537e738..81b5e72ea5 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) @@ -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 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/engine/Split.c b/libgnucash/engine/Split.c index 252babe507..0272b020a5 100644 --- a/libgnucash/engine/Split.c +++ b/libgnucash/engine/Split.c @@ -720,9 +720,15 @@ 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; + if (other) + other->gains_split = NULL; + } + g_object_unref(split); } diff --git a/libgnucash/engine/gnc-date.cpp b/libgnucash/engine/gnc-date.cpp index 2d96648a25..460737b46d 100644 --- a/libgnucash/engine/gnc-date.cpp +++ b/libgnucash/engine/gnc-date.cpp @@ -333,7 +333,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"; @@ -430,7 +431,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/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() { 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 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}