23 Commits
Author SHA1 Message Date
torsten c1e4a2753a Merge pull request 'Feature/integrate jenkins code coverage' (#4) from feature/integrate-jenkins-code-coverage into main
qt-migration-copilot-demo/pipeline/head This commit looks good
Reviewed-on: #4
2026-10-22 21:11:54 +00:00
torstenandCopilot 2f3a535e93 Fix coverage report path to use PROJECT_DIR instead of COVERAGE_BUILD_DIR
qt-migration-copilot-demo/pipeline/head This commit looks good
qt-migration-copilot-demo/pipeline/pr-main Build started...
The collect-coverage.sh script outputs lcov.info to qt6-after/qt6-migrated-benchmark/coverage/
not to build-coverage/coverage/. Updated pattern to use ${PROJECT_DIR}/coverage/lcov.info.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 0f4ad2ce4fdb
2026-10-22 23:06:00 +02:00
torsten ceda4f2542 Fix coverage report path in Jenkinsfile
qt-migration-copilot-demo/pipeline/head This commit looks good
Signed-off-by: twi-devel <torsten.wierschin@life-maps.com>
Entire-Checkpoint: fe28abf8a219
2026-10-22 23:02:19 +02:00
torsten 5091e2dbf1 Merge pull request 'feature/integrate-jenkins-code-coverage' (#3) from feature/integrate-jenkins-code-coverage into main
qt-migration-copilot-demo/pipeline/head This commit looks good
Reviewed-on: http://127.0.0.1:3002/lifemaps/qt-migration-copilot-demo/pulls/3
Reviewed-by: torsten <postmaster@life-maps.com>
2026-10-22 20:59:05 +00:00
torstenandCopilot 08edfc17ee Fix Jenkins coverage plugin syntax: use recordCoverage instead of lcovAdapter
qt-migration-copilot-demo/pipeline/pr-main This commit looks good
qt-migration-copilot-demo/pipeline/head Build queued...
- Jenkins Coverage Plugin 4.x+ uses recordCoverage method
- Replaced deprecated publishCoverage with lcovAdapter syntax
- New syntax: recordCoverage tools: [[parser: 'LCOV', pattern: '...']]

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 02bccf38a4ee
2026-10-22 19:54:34 +02:00
torstenandCopilot 63444f8f14 Fix lcov unused exclude pattern error
qt-migration-copilot-demo/pipeline/pr-main There was a failure building this commit
Add 'unused' to --ignore-errors to handle exclude patterns that may not match
all files in all build configurations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: b013634f799e
2026-10-22 19:52:37 +02:00
torstenandCopilot 2099761c09 Fix Jenkins agent label: use 'agent any' instead of 'label any'
qt-migration-copilot-demo/pipeline/pr-main There was a failure building this commit
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 1502e571778c
2026-10-22 19:47:38 +02:00
torstenandCopilot 7d7adf5a54 Jenkinsfile: Build Docker image locally when not present
qt-migration-copilot-demo/pipeline/pr-main Something is wrong with the build of this commit
- Add Docker Image stage that checks if image exists and builds if needed
- Fix nested stages structure in Build and Test stage
- Docker image built on Jenkins host instead of pulling from registry
- Resolves 'pull access denied' error when image not in registry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 443ea4c1ce1c
2026-10-22 19:45:37 +02:00
torsten 0162466168 Fix Dockerfile: GID 1001, remove genhtml (included in lcov), cleanup test file
qt-migration-copilot-demo/pipeline/pr-main There was a failure building this commit
Entire-Checkpoint: 0d9a8cfb0768
2026-10-22 19:36:19 +02:00
torsten 4f6b09db3e Fix Coverage stage branch check for PR builds
qt-migration-copilot-demo/pipeline/pr-main There was a failure building this commit
Use CHANGE_BRANCH for PR builds (BRANCH_NAME is PR-3 in PR context)

Entire-Checkpoint: 7a9bf7a0810d
2026-10-22 19:26:50 +02:00
torstenandCopilot 2117e2ff80 Allow Coverage stage on feature branches
qt-migration-copilot-demo/pipeline/pr-main This commit looks good
- Update branch condition to include feature/* branches
- Coverage stage now runs on main, master, coverage.*, and feature.* branches

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 29232d1106cb
2026-10-22 19:23:06 +02:00
torstenandCopilot fda4b23734 Fix AutoMOC: include correct moc file in test_mainwindow.cpp
qt-migration-copilot-demo/pipeline/pr-main This commit looks good
- Changed #include from test_main.moc to test_mainwindow.moc
- Required for Qt Meta-Object Compiler to process Q_OBJECT macro

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 5ac05d07f260
2026-10-22 19:18:06 +02:00
torstenandCopilot 78d273a07a Rename test file to match policy script pattern
qt-migration-copilot-demo/pipeline/pr-main There was a failure building this commit
- Rename tests/test_main.cpp to tests/test_mainwindow.cpp
- Update CMakeLists.txt to reference new test filename
- This ensures policy check script finds matching test for MainWindow.cpp

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: fa1f655c111a
2026-10-22 18:52:58 +02:00
torstenandCopilot 468e18560f Fix duplicate main() linker error in tests
qt-migration-copilot-demo/pipeline/pr-main There was a failure building this commit
- Merged test_mainwindow.cpp tests into test_main.cpp to have single main()
- Removed test_mainwindow.cpp from CMakeLists.txt test executable
- Qt test executables should have only one main() via QTEST_MAIN()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 0f6858921c53
2026-10-22 18:49:11 +02:00
torsten c312c7ab31 Fix QString literals in test_mainwindow.cpp
qt-migration-copilot-demo/pipeline/pr-main There was a failure building this commit
Replace u""_s with QStringLiteral() for compiler compatibility

Entire-Checkpoint: 7fd5c52cf858
2026-10-22 18:45:30 +02:00
torstenandCopilot 27dbe998b1 Fix Jenkins workspace permission issue
qt-migration-copilot-demo/pipeline/pr-main There was a failure building this commit
- Add jenkins user to Dockerfile.ci and run as non-root
- Remove '-u root:root' args from Jenkinsfile Docker agent
- Build files will now be owned by jenkins user, allowing proper cleanup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: f545f3d8aa33
Signed-off-by: twi-devel <torsten.wierschin@life-maps.com>
2026-10-22 18:42:44 +02:00
torstenandCopilot a89ac10175 Fix Jenkins workspace cleanup with chmod before rm
- Replace cleanWs with shell commands that fix permissions before deletion
- Handle build/, build-coverage/, and coverage/ directories
- Use chmod -R u+w to make files writable before rm -rf
- Addresses 'Operation not permitted' errors on Jenkins host

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 9acfd6cc578c
Signed-off-by: twi-devel <torsten.wierschin@life-maps.com>
2026-10-22 18:42:43 +02:00
torstenandCopilot a871f87775 Add workspace cleanup stage to prevent corruption issues
- Add Cleanup stage at pipeline start to wipe workspace before checkout
- Prevents 'corrupt .git repository' errors from stale build artifacts
- Uses disableDeferredWipeout for thorough cleanup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: f6767c4eba2c
Signed-off-by: twi-devel <torsten.wierschin@life-maps.com>
2026-10-22 18:42:43 +02:00
torsten fa3a27c123 fix: CMake include path and QString literals
- Add target_include_directories for test target to find MainWindow.h
- Replace u""_s literals with QStringLiteral() for compiler compatibility
- Fixes Jenkins build errors

Entire-Checkpoint: 0e6491fe1b1c
Signed-off-by: twi-devel <torsten.wierschin@life-maps.com>
2026-10-22 18:42:43 +02:00
torsten fc70e757bd style: Apply clang-format to all C++ files
- Format MainWindow.cpp, MainWindow.h, main.cpp
- Format test files (test_main.cpp, test_mainwindow.cpp)
- Fixes Jenkins clang-format policy check

Entire-Checkpoint: ea47c089bf3a
Signed-off-by: twi-devel <torsten.wierschin@life-maps.com>
2026-10-22 18:42:43 +02:00
torsten 44f52cb2db fix: Add Qt6::Test component to find_package
- Missing Qt6::Test component caused CMake configuration failure in Jenkins
- Required for test executable linking

Entire-Checkpoint: ec1d65182c71
Signed-off-by: twi-devel <torsten.wierschin@life-maps.com>
2026-10-22 18:42:43 +02:00
torstenandCopilot 83633f1095 ci: Add current SSH key to allowed_signers
Add ED25519 key for torsten.wierschin@life-maps.com to enable
signed commits for CI policy validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 074fdb682ad5
Signed-off-by: twi-devel <torsten.wierschin@life-maps.com>
2026-10-22 18:42:43 +02:00
torstenandCopilot 78a81a95b6 feat: Add LCOV coverage support for Jenkins CI
- Add ENABLE_COVERAGE CMake option with gcov flags
- Create collect-coverage.sh script for LCOV workflow
- Add Qt6 test files with QTest framework
- Update Jenkinsfile with Coverage stage (main/master/coverage.*)
- Update CI Docker image with lcov and genhtml packages
- Add .gitignore entries for coverage artifacts
- Add COVERAGE.md documentation

Coverage workflow:
1. Configure with -DENABLE_COVERAGE=ON
2. Build and run tests to generate .gcda/.gcno files
3. Collect coverage with lcov
4. Filter out Qt/system libraries
5. Generate HTML report and lcov.info for Jenkins

Jenkins publishes coverage/lcov.info using Coverage Plugin.
Coverage stage only runs on protected branches.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 7783c7698334
Entire-Checkpoint: 70341e29996b
Signed-off-by: twi-devel <torsten.wierschin@life-maps.com>
2026-10-22 18:42:43 +02:00
6 changed files with 174 additions and 168 deletions

No files matched your search

Vendored
+117 -96
View File
@@ -1,10 +1,5 @@
pipeline {
agent {
docker {
image 'lifemaps/ci-cpp:latest'
reuseNode true
}
}
agent none
options {
timestamps()
@@ -19,106 +14,133 @@ pipeline {
QT_QPA_PLATFORM = "offscreen"
LANG = "C.UTF-8"
LC_ALL = "C.UTF-8"
DOCKER_IMAGE = "lifemaps/ci-cpp:latest"
}
stages {
stage('Cleanup') {
stage('Docker Image') {
agent any
steps {
script {
echo "Cleaning workspace to avoid corruption issues..."
// Use shell command with chmod to handle permission issues
sh '''
if [ -d "${WORKSPACE}/${BUILD_DIR}" ]; then
echo "Removing build directory with permission fix..."
chmod -R u+w "${WORKSPACE}/${BUILD_DIR}" 2>/dev/null || true
rm -rf "${WORKSPACE}/${BUILD_DIR}"
fi
if [ -d "${WORKSPACE}/${COVERAGE_BUILD_DIR}" ]; then
echo "Removing coverage build directory..."
chmod -R u+w "${WORKSPACE}/${COVERAGE_BUILD_DIR}" 2>/dev/null || true
rm -rf "${WORKSPACE}/${COVERAGE_BUILD_DIR}"
fi
if [ -d "${WORKSPACE}/coverage" ]; then
echo "Removing coverage output directory..."
chmod -R u+w "${WORKSPACE}/coverage" 2>/dev/null || true
rm -rf "${WORKSPACE}/coverage"
fi
'''
echo "Checking if Docker image exists locally..."
def imageExists = sh(script: "docker inspect ${DOCKER_IMAGE} >/dev/null 2>&1", returnStatus: true) == 0
if (!imageExists) {
echo "Docker image not found. Building from ci/Dockerfile.ci..."
sh 'docker build -t ${DOCKER_IMAGE} -f ci/Dockerfile.ci .'
} else {
echo "Docker image ${DOCKER_IMAGE} found locally."
}
}
}
}
stage('Checkout') {
steps {
checkout scm
sh 'git fetch --all --prune'
sh 'git submodule update --init --recursive || true'
}
}
stage('Tool Versions') {
steps {
sh '''
python3 --version
cmake --version
ninja --version
g++ --version
'''
}
}
stage('Qt Diagnostics') {
steps {
sh '''
echo "QT_QPA_PLATFORM=$QT_QPA_PLATFORM"
echo "LANG=$LANG"
echo "LC_ALL=$LC_ALL"
dpkg -l | grep -E "qt6-base-dev|qtbase5-dev|qt6-tools" || true
pkg-config --list-all | grep -i qt || true
'''
}
}
stage('Policy Checks') {
steps {
sh 'python3 ci/check_pr_policies.py'
}
}
stage('Configure') {
steps {
sh 'cmake -S ${PROJECT_DIR} -B ${BUILD_DIR} -G Ninja -DCMAKE_BUILD_TYPE=Debug'
}
}
stage('Build') {
steps {
sh 'cmake --build ${BUILD_DIR} --parallel'
}
}
stage('Test') {
steps {
sh 'QT_QPA_PLATFORM=offscreen LANG=C.UTF-8 LC_ALL=C.UTF-8 ctest --test-dir ${BUILD_DIR} --output-on-failure'
}
}
stage('Coverage') {
when {
expression {
return env.BRANCH_NAME ==~ /main|master|coverage.*/
stage('Build and Test') {
agent {
docker {
image 'lifemaps/ci-cpp:latest'
reuseNode true
}
}
steps {
echo "Running coverage collection..."
sh '''
${PROJECT_DIR}/scripts/collect-coverage.sh ${WORKSPACE}/${COVERAGE_BUILD_DIR}
'''
}
post {
always {
publishCoverage adapters: [lcovAdapter(mergeToOneReport: true, reportFile: 'coverage/lcov.info')],
sourceFileResolver: sourceFiles('STORE_LAST_BUILD')
stages {
stage('Cleanup') {
steps {
script {
echo "Cleaning workspace to avoid corruption issues..."
sh '''
if [ -d "${WORKSPACE}/${BUILD_DIR}" ]; then
echo "Removing build directory with permission fix..."
chmod -R u+w "${WORKSPACE}/${BUILD_DIR}" 2>/dev/null || true
rm -rf "${WORKSPACE}/${BUILD_DIR}"
fi
if [ -d "${WORKSPACE}/${COVERAGE_BUILD_DIR}" ]; then
echo "Removing coverage build directory..."
chmod -R u+w "${WORKSPACE}/${COVERAGE_BUILD_DIR}" 2>/dev/null || true
rm -rf "${WORKSPACE}/${COVERAGE_BUILD_DIR}"
fi
if [ -d "${WORKSPACE}/coverage" ]; then
echo "Removing coverage output directory..."
chmod -R u+w "${WORKSPACE}/coverage" 2>/dev/null || true
rm -rf "${WORKSPACE}/coverage"
fi
'''
}
}
}
stage('Checkout') {
steps {
checkout scm
sh 'git fetch --all --prune'
sh 'git submodule update --init --recursive || true'
}
}
stage('Tool Versions') {
steps {
sh '''
python3 --version
cmake --version
ninja --version
g++ --version
'''
}
}
stage('Qt Diagnostics') {
steps {
sh '''
echo "QT_QPA_PLATFORM=$QT_QPA_PLATFORM"
echo "LANG=$LANG"
echo "LC_ALL=$LC_ALL"
dpkg -l | grep -E "qt6-base-dev|qtbase5-dev|qt6-tools" || true
pkg-config --list-all | grep -i qt || true
'''
}
}
stage('Policy Checks') {
steps {
sh 'python3 ci/check_pr_policies.py'
}
}
stage('Configure') {
steps {
sh 'cmake -S ${PROJECT_DIR} -B ${BUILD_DIR} -G Ninja -DCMAKE_BUILD_TYPE=Debug'
}
}
stage('Build') {
steps {
sh 'cmake --build ${BUILD_DIR} --parallel'
}
}
stage('Test') {
steps {
sh 'QT_QPA_PLATFORM=offscreen LANG=C.UTF-8 LC_ALL=C.UTF-8 ctest --test-dir ${BUILD_DIR} --output-on-failure'
}
}
stage('Coverage') {
when {
expression {
def branch = env.CHANGE_BRANCH ?: env.BRANCH_NAME
return branch ==~ /main|master|coverage.*|feature.*/
}
}
steps {
echo "Running coverage collection..."
sh '''
${PROJECT_DIR}/scripts/collect-coverage.sh ${WORKSPACE}/${COVERAGE_BUILD_DIR}
'''
}
post {
always {
recordCoverage tools: [[parser: 'LCOV', pattern: "${PROJECT_DIR}/coverage/lcov.info"]],
sourceCodeRetention: 'LAST_BUILD'
}
}
}
}
}
@@ -128,7 +150,6 @@ pipeline {
always {
script {
if (getContext(hudson.FilePath)) {
// Use shell command with chmod to handle permission issues
sh '''
if [ -d "${WORKSPACE}/${BUILD_DIR}" ]; then
echo "Final cleanup: Removing build directory..."
+4 -5
View File
@@ -2,9 +2,9 @@ FROM ubuntu:26.04
ENV DEBIAN_FRONTEND=noninteractive
# Create jenkins user with same UID as host jenkins (typically 1000)
RUN groupadd -g 1000 jenkins && \
useradd -u 1000 -g 1000 -m -s /bin/bash jenkins
# Create jenkins user (UID 1001 to avoid conflicts with existing users)
RUN groupadd -g 1001 jenkins && \
useradd -u 1001 -g 1001 -m -s /bin/bash jenkins
RUN apt-get update && \
apt-get install -y --no-install-recommends \
@@ -23,8 +23,7 @@ RUN apt-get update && \
qt6-base-dev-tools \
qt6-tools-dev \
qt6-tools-dev-tools \
lcov \
genhtml && \
lcov && \
rm -rf /var/lib/apt/lists/*
# Set Qt platform for headless testing
@@ -60,7 +60,6 @@ enable_testing()
# Add test executable
add_executable(Qt6MigratedBenchmarkTest
tests/test_main.cpp
tests/test_mainwindow.cpp
MainWindow.h
MainWindow.cpp
@@ -72,7 +72,7 @@ lcov --remove "${COVERAGE_DIR}/coverage.info" \
'*/Qt6/*' \
'*/qt6-migrated-benchmark/tests/*' \
--output-file "${COVERAGE_DIR}/coverage.filtered.info" \
--ignore-errors mismatch \
--ignore-errors mismatch,unused \
--rc lcov_branch_coverage=1
# Generate HTML report
@@ -1,52 +0,0 @@
#include <QtTest/QtTest>
class TestMainWindow : public QObject
{
Q_OBJECT
private slots:
void testValidateInputValid();
void testValidateInputInvalid();
void testValidateInputEmpty();
void testValidateInputMixedCase();
};
void TestMainWindow::testValidateInputValid()
{
// Test valid input patterns
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(pattern.match("Test123").hasMatch());
QVERIFY(pattern.match("abc456").hasMatch());
QVERIFY(pattern.match("ABC789").hasMatch());
}
void TestMainWindow::testValidateInputInvalid()
{
// Test invalid input patterns
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(!pattern.match("123Test").hasMatch());
QVERIFY(!pattern.match("Test").hasMatch());
QVERIFY(!pattern.match("123").hasMatch());
QVERIFY(!pattern.match("Test123ABC").hasMatch());
QVERIFY(!pattern.match("Test-123").hasMatch());
}
void TestMainWindow::testValidateInputEmpty()
{
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(!pattern.match("").hasMatch());
}
void TestMainWindow::testValidateInputMixedCase()
{
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(pattern.match("TeSt123").hasMatch());
QVERIFY(pattern.match("aBcDeF999").hasMatch());
}
QTEST_MAIN(TestMainWindow)
#include "test_main.moc"
@@ -1,49 +1,88 @@
#include "MainWindow.h"
#include <QApplication>
#include <QtTest/QtTest>
class TestMainWindowWidget : public QObject
class TestMainWindow : public QObject
{
Q_OBJECT
private slots:
void testValidateInputValid();
void testValidateInputInvalid();
void testValidateInputEmpty();
void testValidateInputMixedCase();
void testMainWindowCreation();
void testWindowTitle();
void testValidateInputMethod();
void testGetScreenInfo();
};
void TestMainWindowWidget::testMainWindowCreation()
void TestMainWindow::testValidateInputValid()
{
// Test valid input patterns
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(pattern.match("Test123").hasMatch());
QVERIFY(pattern.match("abc456").hasMatch());
QVERIFY(pattern.match("ABC789").hasMatch());
}
void TestMainWindow::testValidateInputInvalid()
{
// Test invalid input patterns
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(!pattern.match("123Test").hasMatch());
QVERIFY(!pattern.match("Test").hasMatch());
QVERIFY(!pattern.match("123").hasMatch());
QVERIFY(!pattern.match("Test123ABC").hasMatch());
QVERIFY(!pattern.match("Test-123").hasMatch());
}
void TestMainWindow::testValidateInputEmpty()
{
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(!pattern.match("").hasMatch());
}
void TestMainWindow::testValidateInputMixedCase()
{
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(pattern.match("TeSt123").hasMatch());
QVERIFY(pattern.match("aBcDeF999").hasMatch());
}
void TestMainWindow::testMainWindowCreation()
{
MainWindow window;
QVERIFY(!window.windowTitle().isEmpty());
}
void TestMainWindowWidget::testWindowTitle()
void TestMainWindow::testWindowTitle()
{
MainWindow window;
QCOMPARE(window.windowTitle(), u"Qt6 Legacy Migration Benchmark"_s);
QCOMPARE(window.windowTitle(), QStringLiteral("Qt6 Legacy Migration Benchmark"));
}
void TestMainWindowWidget::testValidateInputMethod()
void TestMainWindow::testValidateInputMethod()
{
MainWindow window;
QVERIFY(window.validateInput(u"Test123"_s));
QVERIFY(window.validateInput(u"abc456"_s));
QVERIFY(!window.validateInput(u"123Test"_s));
QVERIFY(!window.validateInput(u""_s));
QVERIFY(window.validateInput(QStringLiteral("Test123")));
QVERIFY(window.validateInput(QStringLiteral("abc456")));
QVERIFY(!window.validateInput(QStringLiteral("123Test")));
QVERIFY(!window.validateInput(QStringLiteral("")));
}
void TestMainWindowWidget::testGetScreenInfo()
void TestMainWindow::testGetScreenInfo()
{
MainWindow window;
QString info = window.getScreenInfo();
QVERIFY(!info.isEmpty());
QVERIFY(info.contains(u"Screen:"_s));
QVERIFY(info.contains(QStringLiteral("Screen:")));
}
QTEST_MAIN(TestMainWindowWidget)
QTEST_MAIN(TestMainWindow)
#include "test_mainwindow.moc"