Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/cmakebuild_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,35 @@ jobs:
MYAPP_TEMPLATE_COMPILERCHOICE_CLANG: 1
run: ./run_all_tests.sh

- name: upload_clang_qt5_coverage_reports
uses: actions/upload-artifact@v4
with:
name: "${{ env.our_nameid }}.clang.qt5.coverage_reports"
path: coverage_reports/
if-no-files-found: error
retention-days: 7
overwrite: false

- name: clear_clang_qt5_coverage_reports
run: rm -rf coverage_reports

- name: run_all_tests_qt6_android
env:
MYAPP_TEMPLATE_BUILD_ANDROID: 1
run: ./run_all_tests.sh

- name: upload_qt6_android_coverage_reports
uses: actions/upload-artifact@v4
with:
name: "${{ env.our_nameid }}.gcc.qt6.android.coverage_reports"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm delighted to see that ${{ env.our_nameid }} is available for this kind of re-use.

path: coverage_reports/
if-no-files-found: error
retention-days: 7
overwrite: false

- name: clear_qt6_android_coverage_reports
run: rm -rf coverage_reports

# Clang Qt6 build is the LAST one, so the artifact upload will have its commands:
- name: run_all_tests_clang
env:
Expand All @@ -65,3 +89,12 @@ jobs:
retention-days: 7
# If overwrite==false, the action will fail if an artifact for the given name already exists.
overwrite: false

- name: upload_clang_coverage_reports
uses: actions/upload-artifact@v4
with:
name: "${{ env.our_nameid }}.clang.qt6.coverage_reports"
path: coverage_reports/
if-no-files-found: error
retention-days: 7
overwrite: false
15 changes: 15 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ jobs:
steps:
- uses: actions/checkout@v1

- name: compute nameid for this run
env:
nameidinput: "${{ github.head_ref || github.ref_name }}"
run: |
echo "our_nameid=${nameidinput//\//-}" >> $GITHUB_ENV

# Remove apt repos that are known to break from time to time
# See https://github.com/actions/virtual-environments/issues/323
- name: Remove broken apt repos
Expand All @@ -35,6 +41,15 @@ jobs:
# For a clang (not gcc) qt6 build, see: cmakebuild_linux.yml
run: ./run_all_tests.sh

- name: upload_gcc_coverage_reports
uses: actions/upload-artifact@v4
with:
name: "${{ env.our_nameid }}.gcc.qt6.coverage_reports"
path: coverage_reports/
if-no-files-found: error
retention-days: 7
overwrite: false

# This one must go last, because it does the AppImage tests.
- name: run_all_tests_gcc_qt5
env:
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
dl_third_party
build
cbuild
cbuild_*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for this!

AppImage_staging
linuxdeployqt-6-x86_64.AppImage
gui_test.log
Expand All @@ -12,3 +14,7 @@ aqtinstall.log

# Qt Creator User File
*.user

# code coverage-related:
**/*.profraw
coverage_reports
37 changes: 37 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,43 @@ set(CMAKE_AUTOUIC
ON
)

if (PROJECT_IS_TOP_LEVEL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#TIL PROJECT_IS_TOP_LEVEL. very nice.

per cmake docs, that was added in cmake 3.21

and we have:

cmake_minimum_required(
  VERSION 3.24
)

so this is great!

set(MYAPP_CLANG_COVERAGE_FLAGS
" -fprofile-instr-generate \
-fcoverage-mapping \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case it's of interest, I'll mention I also tried enabling MC/DC coverage for Clang (adding compile flag -fcoverage-mcdc and giving -show-mcdc to llvm-cov), but oddly that caused the Android build to fail with clang++: error: unknown argument: '-fcoverage-mcdc'. I've noticed the Android build at one point uses a different version of Clang, and with different flags than the main Linux build. I'm afraid I may not immediately be able to prioritize troubleshooting this specific issue further, but where I left it was wondering how MYAPP_CLANG_COVERAGE_FLAGS are even in effect in the Android build, given it doesn't use MYAPP_TEMPLATE_COMPILERCHOICE_CLANG: 1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for these notes. It is an interesting question as to why clang flags are getting "seen"/used during the Android build.

I should consider adding some clarifying comments in various spots in the build scripts.... along these lines:

MYAPP_TEMPLATE_COMPILERCHOICE_CLANG controls local/"normal" (host-to-same-host) compiler option.

Compiling for Android necessarily involves a cross-compiler (host and target machine are different architecture).

The Android compiler is determined by whatever gets downloaded in the Android SDK/NDK/"kit". Refer to tools/ci/install_android_kits.sh

Apparently the SDK/NDK/"kit" provides a clang-based cross-compiler.

Given that we don't have a strong need for MC/DC at this time, I think we can defer that addition for some later PR. Whenever we decide to experiment with adding MC/DC options, then I see at least two paths forward:

  • extend the CMake logic where you used if (${CMAKE_C_COMPILER_ID} STREQUAL Clang) to add an additional constraint along the lines of "and also not Android target."
  • ... or perhaps extend the logic to be based instead on clang compiler version, if it turns out that only versions after some version number have MC/DC.

"
)
set(MYAPP_GCC_COVERAGE_FLAGS
" --coverage"
)
if (${CMAKE_C_COMPILER_ID} STREQUAL Clang)
string(
APPEND
CMAKE_C_FLAGS_DEBUG
"${MYAPP_CLANG_COVERAGE_FLAGS}"
)
elseif(${CMAKE_C_COMPILER_ID} STREQUAL GNU)
string(
APPEND
CMAKE_C_FLAGS_DEBUG
"${MYAPP_GCC_COVERAGE_FLAGS}"
)
endif()
if (${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)
string(
APPEND
CMAKE_CXX_FLAGS_DEBUG
"${MYAPP_CLANG_COVERAGE_FLAGS}"
)
elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
string(
APPEND
CMAKE_CXX_FLAGS_DEBUG
"${MYAPP_GCC_COVERAGE_FLAGS}"
)
endif()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no reason to believe that these new compiler flags would interfere with the recently-added ASan flags, but just to be super confident, I also used the "Bad Code" examples from my prior PR #130 and inserted the bad code (locally only) to make sure ASan still works when code coverage is in the mix. All seems to cooperate just fine!

endif() # PROJECT_IS_TOP_LEVEL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"note on a minor improvement/clean-up": in a follow-up PR, I have moved this block of compiler settings to a new file https://github.com/219-design/qt-qml-project-template-with-ci/pull/139/files#r2599259437


# ##############################################################################
# Begin section: based on the book "Professional CMake" by Craig Scott
#
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ such as:
- runtime "flexible asserts" to ensure you do not miss any QML runtime warnings
- a wrapper script to make iterating on QML layouts painless with qmlscene
- test-runner code to run any unit tests you add. (googletest is provided)
- instrumentation to check code coverage from your tests and generate reports
- a basic '.github/workflow' to run tests on github for each commit
- a basic GUI Test that launches the app in CI using Xvfb
- automated Linux deployment via AppImage folder
Expand Down
2 changes: 1 addition & 1 deletion cookiecutter/init_repo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo

THISDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

ln -sf src/libstyles/imports/libstyles/images src/lib_app/qml/images
ln -sf ../../../src/libstyles/imports/libstyles/images src/lib_app/qml/images
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dear Santa Claus, please bring me whatever missing conceptual knowledge will finally turn me into a competent user of ln

ln -sf .qmake.conf qmake.conf
ln -sf .clang-format clang-format
ln -sf macos dl_third_party/Qt_desktop/6.5.3/clang_64
Expand Down
12 changes: 12 additions & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ This involves five key steps:
- [Generate compile\_commands.json during CMake build](https://github.com/219-design/qt-qml-project-template-with-ci/blob/b0abc1b/build_cmake_app.sh#L95-L118)
- [Publish compile\_commands.json as a CI build artifact](https://github.com/219-design/qt-qml-project-template-with-ci/blob/4b02a48/.github/workflows/cmakebuild_linux.yml#L54-L67)

### Check Code Coverage

- Enable compiler code coverage instrumentation and generate `.html` code coverage reports (for some build types).
- Building with coverage instrumentation is [supported (and enabled automatically) for CMake debug builds with either Clang or GCC compilers](https://github.com/219-design/qt-qml-project-template-with-ci/blob/699819e859fe3251bd15d63add08e19aa52bf282/CMakeLists.txt#L103).
- The GCC case uses [`gcov`](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html) for coverage and [`gcovr`](https://gcovr.com/) for report generation.
- The Clang case uses [Clang Source-based Code Coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html) for coverage and report generation.
- We use Clang's native "source-based" coverage to give an example of using it and for a second angle on coverage, but note Clang advertises some "`gcov`-compatibility" options that might be worth exploring in multi-compiler situations where sharing coverage-processing code and report formats is preferred to having more perspectives.
- `run_all_tests.sh` [generates `.html` code coverage reports for those supported build types](https://github.com/219-design/qt-qml-project-template-with-ci/blob/699819e859fe3251bd15d63add08e19aa52bf282/run_all_tests.sh#L92).
- These reports are put in sub-directories with timestamp names, created within `coverage_reports/clang-llvm` and `coverage_reports/gcc-gcov` for Clang and GCC builds, respectively.
- Within each timestamp-named sub-directory, you can open `index.html` to view the report summary. Following hyperlinks from there, you can find per-directory reports and per-file reports that show exactly which lines are covered.
- For each supported `run_all_tests.sh` run in CI, [the generated](https://github.com/219-design/qt-qml-project-template-with-ci/blob/699819e859fe3251bd15d63add08e19aa52bf282/.github/workflows/cmakebuild_linux.yml#L42) [coverage reports](https://github.com/219-design/qt-qml-project-template-with-ci/blob/699819e859fe3251bd15d63add08e19aa52bf282/.github/workflows/cmakebuild_linux.yml#L59) [are saved](https://github.com/219-design/qt-qml-project-template-with-ci/blob/699819e859fe3251bd15d63add08e19aa52bf282/.github/workflows/cmakebuild_linux.yml#L93) [as an artifact](https://github.com/219-design/qt-qml-project-template-with-ci/blob/699819e859fe3251bd15d63add08e19aa52bf282/.github/workflows/main.yml#L44).

## Leverage Tools For Finding Warnings/Errors/UB/Bugs

- [Enable ASan in CMake Debug Build](https://github.com/219-design/qt-qml-project-template-with-ci/blob/b0abc1b/cmake_include/unix_settings.cmake#L44-L80)
Expand Down
53 changes: 53 additions & 0 deletions run_all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ fi

if [[ -n ${MYAPP_TEMPLATE_PREFER_QMAKE-} ]]; then
./build_app.sh
DEBUG_BUILD_DIR="${THISDIR}/build"
else
# Run a Release build (except where it doesn't make sense)
if [[ "$OSTYPE" != "darwin"* ]]; then
Expand All @@ -70,6 +71,7 @@ else
fi

./build_cmake_app.sh # <-- the default invocation (no args) will build debug.
DEBUG_BUILD_DIR="${THISDIR}/cbuild"

if [[ -z ${MYAPP_TEMPLATE_QT5-} ]]; then
# Nice-to-have: figure out why cmake-qt6 moved `app` such that we now need this.
Expand All @@ -78,11 +80,60 @@ else
fi
fi

# Note coverage-related operations are structured to be no-ops unless the corresponding
# instrumentation is present.
COVERAGE_RUN_TIMESTAMP="$(date +%Y-%m-%d_%H%M%S_%N)"
CLANG_COVERAGE_DATA_DIR="${DEBUG_BUILD_DIR}/coverage_data/${COVERAGE_RUN_TIMESTAMP}"
export LLVM_PROFILE_FILE="${CLANG_COVERAGE_DATA_DIR}/%p.profraw"

# Clear any existing gcov data so it does not contribute to this run's coverage results.
find ${DEBUG_BUILD_DIR} -name "*.gcda" -o -name "*.gcov" -delete

process_coverage_data() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"note on a minor improvement/clean-up": in a follow-up PR, I have moved this report-generation logic to its own separate script.

In order to do that, it was necessary for me to tackle some long-overdue cleanup regarding "single source of truth" for build output folder. It is no fault of yours that isolating the report-generation was not exactly readily feasible at the time of your PR.

see: https://github.com/219-design/qt-qml-project-template-with-ci/pull/139/files#r2599266472

if [[ -d "${CLANG_COVERAGE_DATA_DIR}" ]]; then
llvm-profdata merge $(find ${CLANG_COVERAGE_DATA_DIR} -name "*.profraw") \
-o ${CLANG_COVERAGE_DATA_DIR}/coverage.profdata
CLANG_COVERAGE_REPORTS_DIR="${THISDIR}/coverage_reports/clang-llvm/${COVERAGE_RUN_TIMESTAMP}"
mkdir -p ${CLANG_COVERAGE_REPORTS_DIR}

# Individually prefacing each relevant binary file with "-object" is seemingly
# unavoidable for llvm-cov. In spite of the fact it's willing to treat a single
# non-option argument as a binary file, it's not willing to do that for files
# after the first, nor is it willing to accept a directory.
LLVM_COV_BINARY_FILE_ARGS=""
for BINARY_FILE in $(find -L ${DEBUG_BUILD_DIR}/stage -type f); do
LLVM_COV_BINARY_FILE_ARGS+=" -object ${BINARY_FILE}"
done

llvm-cov show \
-instr-profile ${CLANG_COVERAGE_DATA_DIR}/coverage.profdata \
${LLVM_COV_BINARY_FILE_ARGS} \
-sources ${THISDIR}/src \
-output-dir=${CLANG_COVERAGE_REPORTS_DIR} \
-format=html \
-show-branches=count \
-show-line-counts-or-regions \
-show-directory-coverage
fi
if [[ -n $(find ${DEBUG_BUILD_DIR} -name "*.gcda") ]]; then
GCC_COVERAGE_REPORTS_DIR="${THISDIR}/coverage_reports/gcc-gcov/${COVERAGE_RUN_TIMESTAMP}"
mkdir -p ${GCC_COVERAGE_REPORTS_DIR}
pushd "${DEBUG_BUILD_DIR}"
gcovr \
--root ${THISDIR} \
--filter "${THISDIR}/src" \
--html-nested ${GCC_COVERAGE_REPORTS_DIR}/index.html \
--decisions
popd # ${DEBUG_BUILD_DIR}
fi
}

# run all test binaries that got built in the expected dir:
tools/auto_test/run_cpp_auto_tests.sh

if [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" ]]; then
#################### EARLY EXIT FOR MICROSOFT WINDOWS. (TODO: tools/gui_test for WIN32)
process_coverage_data
echo 'EARLY EXIT FOR MICROSOFT WINDOWS. (TODO: tools/gui_test for WIN32)'
exit 0
fi
Expand All @@ -108,6 +159,8 @@ fi
# run gui tests which execute the actual app binary:
tools/gui_test/launch_gui_for_display.sh "${XDISPLAY}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am very pleasantly surprised and delighted that when the app launches due to launch_gui_for_display.sh, the lines of code executed by launching the actual GUI app are also included in the coverage report!

I suppose I shouldn't be surprised. But I got "tunnel vision" thinking coverage was somehow limited to unit tests. But of course there is nothing inherent in gcovr or llvm-cov that cause such a limitation.


process_coverage_data

if [[ -n ${MYAPP_TEMPLATE_BUILD_APPIMAGE-} ]]; then
# this MUST happen last because (on the C.I. server) it destroys folders (intentionally)
tools/gui_test/test_AppImage.sh "${XDISPLAY}"
Expand Down
3 changes: 3 additions & 0 deletions tools/ci/provision.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ sudo apt-get --assume-yes install libc6:i386 libc6-dev:i386
# libstdc++-10-dev was added for the sake of clang. see: https://stackoverflow.com/q/26333823/10278
sudo apt-get --assume-yes install \
build-essential \
clang \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've assumed (without verifying) that using clang, gcovr, and llvm isn't a concern licensing-wise since they're used as tools, not ingredients. Please let me know if there is any particular due diligence I should do to add these dependencies.

clang-format-19 \
cmake \
curl \
graphviz \
g++-11 \
gcovr \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intend to leave gcovr here in the apt-get install list.

As an aside (or "note to self"), I was also able to install (and get a specific version) by doing:

pip install git+https://github.com/gcovr/gcovr.git@87771a89336904e6c4a670b4096f8b8a814b8c22

gdb \
git \
gnupg \
Expand Down Expand Up @@ -74,6 +76,7 @@ sudo apt-get --assume-yes install \
libxcb-xkb1 \
libxkbcommon-x11-0 \
libxkbcommon0 \
llvm \
mesa-common-dev \
openjdk-8-jdk \
openjdk-8-jre \
Expand Down
2 changes: 2 additions & 0 deletions tools/ci/provision_mac.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ brew \
--overwrite python@3.12 \
clang-format@21 \
fontconfig \
gcovr \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added coverage dependencies to provision_mac.sh, and I don't think I've done anything more platform-dependent than existing code in CMakeLists.txt and run_all_tests.sh, but coverage on Mac is untested since the only Mac build in CI uses qmake. Feel free to let me know any thoughts on if I should replace or extend .github/workflows/mac2023.yml with a CMake build to run Mac coverage in CI.

gdbm \
glib \
gnu-sed \
graphviz \
grep \
libtool \
llvm \
p7zip \
pcre \
pcre2 \
Expand Down
Loading