gnucash stable: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Fri Dec 8 18:07:32 EST 2023


Updated	 via  https://github.com/Gnucash/gnucash/commit/e22a57ad (commit)
	 via  https://github.com/Gnucash/gnucash/commit/d92d97ae (commit)
	 via  https://github.com/Gnucash/gnucash/commit/1e85d0b1 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/6834cb50 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a3f14759 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/dd0b72cd (commit)
	 via  https://github.com/Gnucash/gnucash/commit/087f1350 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/6940488d (commit)
	 via  https://github.com/Gnucash/gnucash/commit/f67b53a4 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/fa119f8d (commit)
	 via  https://github.com/Gnucash/gnucash/commit/b9b7a9e0 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e17ba3cc (commit)
	 via  https://github.com/Gnucash/gnucash/commit/2234fa43 (commit)
	from  https://github.com/Gnucash/gnucash/commit/6bc12898 (commit)



commit e22a57ad26b856f6db52cc0b6cc0450cb263b8f1
Merge: 6bc12898a8 d92d97aef6
Author: John Ralls <jralls at ceridwen.us>
Date:   Fri Dec 8 14:56:29 2023 -0800

    Merge John Ralls's 'asan' into stable.


commit d92d97aef618679b1f0309d648c507d5913c63d6
Author: John Ralls <jralls at ceridwen.us>
Date:   Tue Dec 5 17:00:52 2023 -0800

    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.

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}$<$<CONFIG:Asan>:;${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)

commit 1e85d0b1158ecfd186fa898f5878efe60f0df708
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Dec 4 11:34:10 2023 -0800

    Add workflow job to generate a coverage report.
    
    URI is https://gnucash.github.io/

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 at 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 at 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 at v2

commit 6834cb502501a330aca868f7a84dcb9272d0b90e
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Dec 4 10:43:50 2023 -0800

    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, <Builddir>/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
    <Builddir>/Coverage-HTML showing coverage by source directory (the
    directories in <Builddir> 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.

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("$<$<CONFIG:Debug>:${COVERAGE_COMPILE_OPTION}>")
+  add_link_options("$<$<CONFIG:Debug>:${COVERAGE_COMPILE_OPTION}>")
+  list(APPEND ASAN_LINK_OPTIONS ${COVERAGE_COMPILE_OPTION})
 endif()
 set(ASAN_COMPILE_OPTIONS -g ${ASAN_LINK_OPTIONS})
 add_compile_options("$<$<CONFIG:Asan>:${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}

commit a3f14759ab8c40e60c205166c43c7edb32bfbaa8
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Nov 25 11:17:14 2023 -0800

    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.

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("$<$<CONFIG:Asan>:${ASAN_COMPILE_OPTIONS}>")
-add_link_options("$<$<CONFIG:Asan>:${ASAN_BUILD_OPTIONS}>")
+add_link_options("$<$<CONFIG:Asan>:${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 "$<$<CONFIG:Asan>:;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 "$<IF:$<CONFIG:Asan>,${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}$<$<CONFIG:Asan>:;${ASAN_DYNAMIC_LIB_ENV}>"
+            "${GUILE_ENV}$<$<CONFIG:Asan>:;${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 "$<IF:$<CONFIG:Asan>,${ENVVARS};ASAN_OPTIONS=fast_unwind_on_malloc=0,${ENVVARS}>")
+  set_tests_properties(${_TARGET} PROPERTIES ENVIRONMENT "${ENVVARS}$<$<CONFIG:Asan>:;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 "$<IF:$<CONFIG:Asan>,${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}$<$<CONFIG:Asan>:;${ASAN_DYNAMIC_LIB_ENV};ASAN_OPTIONS=${ASAN_TEST_OPTIONS}>;${ARGN}>")
 endfunction()
 
 function(gnc_add_scheme_tests _SOURCE_FILES)

commit dd0b72cdb5a6419dbcb64b49f5c4249a322e1f83
Author: John Ralls <jralls at ceridwen.us>
Date:   Fri Nov 17 15:20:29 2023 -0800

    Fix global array index out of range.
    
    Tests must set the account type to avoid errors in xaccAccountOrder.

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() {

commit 087f135085b51aefed7464df291c4f37e862f229
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Nov 9 15:06:15 2023 -0800

    Add coverage option for Asan builds.

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("$<$<CONFIG:Asan>:${ASAN_COMPILE_OPTIONS}>")
 add_link_options("$<$<CONFIG:Asan>:${ASAN_BUILD_OPTIONS}>")

commit 6940488d2df518d8fd6c168b0bd3bd088811c4c7
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Wed Nov 1 21:49:48 2023 +0800

    recurrencePeriodTypeToString and recurrenceWeekendAdjustToString g_strdup only when necessary
    
    ... for sql use only. xml use assumes they return a const char*.

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. */

commit f67b53a44041be62acfa61b1ff7f1f2d4fc1b0d8
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Oct 30 14:03:50 2023 -0700

    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.

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

commit fa119f8d17bb0f4daaa775209d8c4b913f96bdec
Author: John Ralls <jralls at ceridwen.us>
Date:   Fri Oct 27 14:07:57 2023 -0700

    Split test other

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);

commit b9b7a9e0094ce89b09b648310da876e1133bb0d5
Author: John Ralls <jralls at ceridwen.us>
Date:   Sun Oct 22 14:55:07 2023 -0700

    Add CI job for Asan build.

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 at 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 at v3
-      if: failure()
-      with:
-        name: TestLog
-        path: ${{ github.workspace }}/build/Testing/Temporary/LastTest.log
+      - name: Checkout
+        uses: actions/checkout at 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 at 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 at 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 at v3
+        if: failure()
+        with:
+          name: TestLog
+          path: ${{ github.workspace }}/build/Testing/Temporary/LastTest.log

commit e17ba3cc002bb19f1eb6a966462ce83bb3fa1959
Author: John Ralls <jralls at ceridwen.us>
Date:   Sun Oct 22 10:15:44 2023 -0700

    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.

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<DbType::DBI_SQLITE>::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<DbType::DBI_SQLITE>::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<uint8_t>(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<uint8_t>(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 <hampton at employees.org>
  */
+#include "qof-string-cache.h"
 #include <glib.h>
 
 #include <config.h>
@@ -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

commit 2234fa433e9e1b343e7b189472804bc431eabbb0
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Oct 21 11:05:03 2023 -0700

    Add Asan build type that enables the Address and UB sanitizers.
    
    Uses generator statements instead of CMAKE_<TYPE>_FLAGS_ASAN to support multiconfig generators like Xcode.

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("$<$<CONFIG:Asan>:${ASAN_COMPILE_OPTIONS}>")
+add_link_options("$<$<CONFIG:Asan>:${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 "$<IF:$<CONFIG:Asan>,${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}$<$<CONFIG:Asan>:;${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 "$<IF:$<CONFIG:Asan>,${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}$<$<CONFIG:Asan>:;${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 "$<IF:$<CONFIG:Asan>,${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)



Summary of changes:
 .github/workflows/ci-tests.yml                     |  97 ++++++++++++------
 .github/workflows/coverage.yml                     |  66 ++++++++++++
 CMakeLists.txt                                     |  50 ++++++++++
 bindings/guile/gnc-optiondb.i                      |   4 +-
 bindings/guile/test/srfi64-extras.scm              |   2 +-
 bindings/python/CMakeLists.txt                     |   1 +
 bindings/python/tests/CMakeLists.txt               |  10 +-
 common/cmake_modules/GncAddSchemeTargets.cmake     |  17 ++--
 common/cmake_modules/GncAddTest.cmake              |  80 ++++++++++-----
 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 +
 gnucash/import-export/bi-import/CMakeLists.txt     |   4 +
 gnucash/import-export/csv-exp/CMakeLists.txt       |   4 +
 gnucash/import-export/csv-imp/CMakeLists.txt       |   4 +
 .../import-export/customer-import/CMakeLists.txt   |   4 +
 gnucash/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 +
 gnucash/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/dbi/gnc-backend-dbi.cpp         |   4 +-
 libgnucash/backend/sql/CMakeLists.txt              |   4 +
 libgnucash/backend/xml/CMakeLists.txt              |   8 ++
 libgnucash/backend/xml/gnc-xml-backend.cpp         |   7 +-
 libgnucash/backend/xml/test/CMakeLists.txt         |  54 ++--------
 libgnucash/core-utils/CMakeLists.txt               |   4 +
 libgnucash/engine/CMakeLists.txt                   |   4 +
 libgnucash/engine/Split.c                          |  12 ++-
 libgnucash/engine/gnc-date.cpp                     |   7 +-
 libgnucash/engine/gnc-timezone.cpp                 |   4 +-
 libgnucash/engine/qofbook.cpp                      |  25 ++++-
 libgnucash/engine/test/gtest-import-map.cpp        |   7 ++
 libgnucash/engine/test/test-guid.cpp               |   2 +-
 libgnucash/engine/test/test-qofbook.c              |   3 -
 libgnucash/engine/test/test-qofobject.c            |   6 +-
 libgnucash/gnc-module/CMakeLists.txt               |   4 +
 libgnucash/tax/CMakeLists.txt                      |   4 +
 47 files changed, 545 insertions(+), 135 deletions(-)
 create mode 100644 .github/workflows/coverage.yml
 create mode 100644 common/cmake_modules/GncCoverage.cmake



More information about the gnucash-changes mailing list