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>
This commit is contained in:
torstenandCopilot committed 2026-10-22 18:42:43 +02:00
1 parent 9e3c6eef2e
commit 78a81a95b6
19 files changed
+734 -235

No files matched your search

Whitespace-only changes.
@@ -1,33 +0,0 @@
cmake_minimum_required(VERSION 3.16)
project(Qt6MigratedBenchmarkTests LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Test)
add_executable(main_test
main_test.cpp
)
target_link_libraries(main_test PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Test
)
add_test(NAME main_test COMMAND main_test)
add_executable(MainWindow_test
MainWindow_test.cpp
)
target_link_libraries(MainWindow_test PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Test
)
add_test(NAME MainWindow_test COMMAND MainWindow_test)
@@ -1,75 +0,0 @@
#include <QRect>
#include <QRegularExpression>
#include <QScreen>
#include <QString>
#include <QTest>
class MainWindowPatternTest : public QObject
{
Q_OBJECT
private slots:
void testPatternValidLettersThenDigits();
void testPatternInvalidDigitsThenLetters();
void testPatternInvalidSpecialCharacters();
void testScreenGeometryExtraction();
void testRegularExpressionContainsMethod();
};
void MainWindowPatternTest::testPatternValidLettersThenDigits()
{
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(pattern.match(QString::fromUtf8("Test123")).hasMatch());
QVERIFY(pattern.match(QString::fromUtf8("ABC456")).hasMatch());
QVERIFY(pattern.match(QString::fromUtf8("a1")).hasMatch());
}
void MainWindowPatternTest::testPatternInvalidDigitsThenLetters()
{
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(!pattern.match(QString::fromUtf8("123Test")).hasMatch());
QVERIFY(!pattern.match(QString::fromUtf8("1a")).hasMatch());
}
void MainWindowPatternTest::testPatternInvalidSpecialCharacters()
{
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(!pattern.match(QString::fromUtf8("Test@123")).hasMatch());
QVERIFY(!pattern.match(QString::fromUtf8("Test_123")).hasMatch());
QVERIFY(!pattern.match(QString::fromUtf8("")).hasMatch());
}
void MainWindowPatternTest::testScreenGeometryExtraction()
{
const QScreen* screen = QGuiApplication::primaryScreen();
if (screen)
{
const QRect geometry = screen->geometry();
QVERIFY(geometry.width() > 0);
QVERIFY(geometry.height() > 0);
const QString info = QString::fromUtf8("Screen: %1x%2").arg(geometry.width()).arg(geometry.height());
QVERIFY(!info.isEmpty());
QVERIFY(info.contains(QString::fromUtf8("Screen:")));
}
}
void MainWindowPatternTest::testRegularExpressionContainsMethod()
{
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
const QString validInput = QString::fromUtf8("Test123");
const QRegularExpressionMatch match = pattern.match(validInput);
QVERIFY(match.hasMatch());
const QString invalidInput = QString::fromUtf8("Invalid");
const QRegularExpressionMatch noMatch = pattern.match(invalidInput);
QVERIFY(!noMatch.hasMatch());
}
QTEST_MAIN(MainWindowPatternTest)
#include "MainWindow_test.moc"
@@ -1,63 +0,0 @@
#include <QRegularExpression>
#include <QString>
#include <QTest>
class MainTest : public QObject
{
Q_OBJECT
private slots:
void testRegularExpressionValidation_data();
void testRegularExpressionValidation();
void testQStringUtf8Conversion();
void testRegularExpressionPattern();
};
void MainTest::testRegularExpressionValidation_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<bool>("expectedMatch");
QTest::newRow("valid_input_Test123") << QString::fromUtf8("Test123") << true;
QTest::newRow("valid_input_ABC456") << QString::fromUtf8("ABC456") << true;
QTest::newRow("valid_input_a1") << QString::fromUtf8("a1") << true;
QTest::newRow("invalid_input_123Test") << QString::fromUtf8("123Test") << false;
QTest::newRow("invalid_input_special") << QString::fromUtf8("Test@123") << false;
QTest::newRow("invalid_input_empty") << QString::fromUtf8("") << false;
}
void MainTest::testRegularExpressionValidation()
{
QFETCH(QString, input);
QFETCH(bool, expectedMatch);
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
const bool matches = pattern.match(input).hasMatch();
QCOMPARE(matches, expectedMatch);
}
void MainTest::testQStringUtf8Conversion()
{
const QString title = QString::fromUtf8("Qt6 Migrated Benchmark");
QCOMPARE(title, QString::fromUtf8("Qt6 Migrated Benchmark"));
const QString label = QString::fromUtf8("Bitte Text wie Test123 eingeben.");
QCOMPARE(label, QString::fromUtf8("Bitte Text wie Test123 eingeben."));
}
void MainTest::testRegularExpressionPattern()
{
QRegularExpression pattern(R"(^[A-Za-z]+\d+$)");
QVERIFY(pattern.isValid());
const QRegularExpressionMatch match = pattern.match(QString::fromUtf8("Test123"));
QVERIFY(match.hasMatch());
const QRegularExpressionMatch noMatch = pattern.match(QString::fromUtf8("123Test"));
QVERIFY(!noMatch.hasMatch());
}
QTEST_MAIN(MainTest)
#include "main_test.moc"
@@ -0,0 +1,52 @@
#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"
@@ -0,0 +1,48 @@
#include <QtTest/QtTest>
#include <QApplication>
#include "MainWindow.h"
class TestMainWindowWidget : public QObject
{
Q_OBJECT
private slots:
void testMainWindowCreation();
void testWindowTitle();
void testValidateInputMethod();
void testGetScreenInfo();
};
void TestMainWindowWidget::testMainWindowCreation()
{
MainWindow window;
QVERIFY(!window.windowTitle().isEmpty());
}
void TestMainWindowWidget::testWindowTitle()
{
MainWindow window;
QCOMPARE(window.windowTitle(), u"Qt6 Legacy Migration Benchmark"_s);
}
void TestMainWindowWidget::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));
}
void TestMainWindowWidget::testGetScreenInfo()
{
MainWindow window;
QString info = window.getScreenInfo();
QVERIFY(!info.isEmpty());
QVERIFY(info.contains(u"Screen:"_s));
}
QTEST_MAIN(TestMainWindowWidget)
#include "test_mainwindow.moc"