Author SHA1 Message Date
torsten ca89560d47 Tests push with new email
qt-migration-copilot-demo/pipeline/head This commit looks good
2026-08-13 10:00:15 +00:00
torsten 1cabf81f7c Adds testfile to check main branch protection 2026-08-13 09:43:57 +00:00
19 changed files with 270 additions and 835 deletions

No files matched your search

-144
View File
@@ -1,144 +0,0 @@
# Add LCOV Coverage for a Qt5-to-Qt6 Migration Project
Work in the supplied Qt5-to-Qt6 migration repository. Inspect the actual repository layout, CMake targets, tests, and `Jenkinsfile` before making changes. Do not assume target names or paths.
## Goal
Add reliable LCOV coverage generation for the Qt6 project so Jenkins Coverage Plugin can consume:
```text
coverage/lcov.info
```
## Repository-specific Jenkins CI integration
The repository contains Jenkins-related CI support files in `./ci/`. Before changing the build or pipeline, inspect and understand all of the following files:
```text
ci/allowed_signers
ci/check_pr_policies.py
ci/Dockerfile.ci
```
Analyze how these files are used by Jenkins, including the CI image, installed tools and versions, signing or trust configuration, pull-request policy checks, workspace-relative paths, user permissions, and any commands or environment variables that affect builds and tests. Adapt the coverage implementation to the existing conventions instead of bypassing them. In particular:
- ensure `lcov`, `genhtml`, Qt6, CMake, CTest, and any required Python/runtime dependencies are available in the Jenkins CI image or document the required image changes;
- preserve and integrate existing policy, signing, and verification checks;
- use the existing Jenkins container/tooling invocation and workspace layout;
- do not weaken, remove, or bypass checks from `ci/check_pr_policies.py` or trust rules from `ci/allowed_signers`;
- update `ci/Dockerfile.ci` and related CI configuration when needed so the coverage stages work reproducibly;
- verify that coverage artifacts remain workspace-relative and are available to later Jenkins stages;
- document any CI-file changes and explain how they support the Jenkins coverage workflow.
If any of these files are absent, inaccessible, or not actually referenced by the current Jenkins setup, report that explicitly and do not invent their contents or usage.
## Requirements
- Do not modify the Qt5 baseline under `qt5-before`.
- Preserve normal non-coverage builds.
- Add an optional CMake coverage mode, disabled by default, for GCC and Clang where supported.
- Use debug symbols and suitable low-optimization coverage flags.
- Ensure Qt6 tests are registered with CTest and run with:
```bash
ctest --test-dir <build-dir> --output-on-failure
```
- Add an executable script, preferably:
```text
scripts/collect-coverage.sh
```
The script must:
- accept an optional build directory, defaulting to `build/coverage`;
- check that `lcov` is installed;
- optionally require `genhtml` when generating HTML;
- create `coverage/lcov.info`;
- optionally create `coverage/html/index.html`;
- include only project source files;
- exclude Qt, system, compiler, build, and external dependency paths;
- use safe error handling such as `set -euo pipefail`;
- fail on missing, empty, or invalid coverage data;
- validate that the tracefile contains valid LCOV records.
- Update `Jenkinsfile` with explicit stages for:
1. configuring the coverage build;
2. compiling;
3. running CTest;
4. collecting LCOV data;
5. validating and archiving coverage artifacts;
6. publishing coverage with the Jenkins Coverage Plugin.
- Configure the plugin to read exactly:
```text
coverage/lcov.info
```
Prefer `recordCoverage` with the LCOV parser when compatible with the existing Jenkins setup. Inspect the current `Jenkinsfile` and follow its conventions.
- The Jenkins build must fail when:
- a test fails;
- LCOV generation fails;
- `coverage/lcov.info` is missing, empty, or invalid.
- Use workspace-relative paths only.
- Preserve generated coverage output outside source control; update `.gitignore` if necessary.
- Do not commit secrets or fake coverage results.
- Update the documentation with:
- required Qt6, CMake, CTest, `lcov`, and `genhtml` versions;
- Debian/Ubuntu installation instructions;
- local build, test, and coverage commands;
- Jenkins Coverage Plugin requirements;
- artifact locations;
- the role of `ci/allowed_signers`, `ci/check_pr_policies.py`, and `ci/Dockerfile.ci` in the Jenkins workflow.
Document and verify this workflow:
```bash
rm -rf build/coverage coverage
cmake -S qt6-after/qt6-migrated-benchmark \
-B build/coverage \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_COVERAGE=ON
cmake --build build/coverage
ctest --test-dir build/coverage \
--output-on-failure
./scripts/collect-coverage.sh build/coverage
```
Expected files:
```text
coverage/lcov.info
coverage/html/index.html
```
Verify, where possible:
- normal configuration and build;
- coverage configuration and build;
- CTest execution;
- LCOV generation;
- LCOV validation;
- HTML report generation;
- ShellCheck for new shell scripts;
- the Jenkins CI image and `./ci/` policy/signing checks relevant to the coverage stages.
At the end, report:
- changed files, including any files under `ci/`;
- coverage-enabled CMake targets;
- exact LCOV and HTML report paths;
- Jenkins publication stage;
- how the `./ci/` files were analyzed and adapted;
- successful checks;
- checks skipped because dependencies were unavailable.
-5
View File
@@ -2,8 +2,3 @@
.*~
*~
build
build-coverage
coverage/
*.gcda
*.gcno
*.gcov
Vendored
+50 -134
View File
@@ -1,5 +1,11 @@
pipeline {
agent none
agent {
docker {
image 'lifemaps/ci-cpp:latest'
reuseNode true
args '-u root:root'
}
}
options {
timestamps()
@@ -9,139 +15,65 @@ pipeline {
environment {
PROJECT_DIR = "qt6-after/qt6-migrated-benchmark"
BUILD_DIR = "build"
COVERAGE_BUILD_DIR = "build-coverage"
CHANGE_TARGET = "${env.CHANGE_TARGET ?: 'origin/main'}"
QT_QPA_PLATFORM = "offscreen"
LANG = "C.UTF-8"
LC_ALL = "C.UTF-8"
DOCKER_IMAGE = "lifemaps/ci-cpp:latest"
}
stages {
stage('Docker Image') {
agent any
stage('Checkout') {
steps {
script {
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."
}
}
checkout scm
sh 'git fetch --all --prune'
sh 'git submodule update --init --recursive || true'
}
}
stage('Build and Test') {
agent {
docker {
image 'lifemaps/ci-cpp:latest'
reuseNode true
}
stage('Tool Versions') {
steps {
sh '''
python3 --version
cmake --version
ninja --version
g++ --version
'''
}
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('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('Tool Versions') {
steps {
sh '''
python3 --version
cmake --version
ninja --version
g++ --version
'''
}
}
stage('Policy Checks') {
steps {
sh 'python3 ci/check_pr_policies.py'
}
}
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('Configure') {
steps {
sh 'cmake -S ${PROJECT_DIR} -B ${BUILD_DIR} -G Ninja -DCMAKE_BUILD_TYPE=Debug'
}
}
stage('Policy Checks') {
steps {
sh 'python3 ci/check_pr_policies.py'
}
}
stage('Build') {
steps {
sh 'cmake --build ${BUILD_DIR} --parallel'
}
}
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'
}
}
}
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'
}
}
}
@@ -150,23 +82,7 @@ pipeline {
always {
script {
if (getContext(hudson.FilePath)) {
sh '''
if [ -d "${WORKSPACE}/${BUILD_DIR}" ]; then
echo "Final cleanup: Removing build directory..."
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 "Final cleanup: 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 "Final cleanup: Removing coverage output..."
chmod -R u+w "${WORKSPACE}/coverage" 2>/dev/null || true
rm -rf "${WORKSPACE}/coverage"
fi
'''
cleanWs deleteDirs: true, disableDeferredWipeout: true
}
}
}
+1 -9
View File
@@ -2,10 +2,6 @@ FROM ubuntu:26.04
ENV DEBIAN_FRONTEND=noninteractive
# 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 \
python3 \
@@ -22,8 +18,7 @@ RUN apt-get update && \
qt6-base-dev \
qt6-base-dev-tools \
qt6-tools-dev \
qt6-tools-dev-tools \
lcov && \
qt6-tools-dev-tools && \
rm -rf /var/lib/apt/lists/*
# Set Qt platform for headless testing
@@ -33,6 +28,3 @@ ENV QT_QPA_PLATFORM=offscreen
RUN git config --global --add safe.directory "*"
WORKDIR /workspace
# Switch to non-root user
USER jenkins
+1 -2
View File
@@ -1,3 +1,2 @@
torsten.wierschin@life-maps.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8degz9DpjybBeBrdvMc50ajsi+fioRETtE59tyzKO+
torsten.wierschin@life-maps.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO+c+9jJKrLpTk4pLg+p1Qo0CjCYzfKfEhXEu0MAlZpj
nkana@outlook.de ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBR68AzU7UtY+NJPIRloEeIlQIZIP3xhvkWB8BediMeQ
torsten.wierschin@life-maps.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMV8yH39fMOQE5BeCsxI8RMUXbDBJvRuS5phTTfrXF0H
Whitespace-only changes.
@@ -10,10 +10,7 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
# Coverage option - disabled by default
option(ENABLE_COVERAGE "Enable code coverage with gcov/lcov" OFF)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Test)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
qt_standard_project_setup(REQUIRES 6.5)
@@ -31,69 +28,13 @@ target_link_libraries(Qt6MigratedBenchmark PRIVATE
Qt6::Widgets
)
# Coverage configuration
if(ENABLE_COVERAGE)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
message(STATUS "Coverage enabled for ${CMAKE_CXX_COMPILER_ID}")
# Add coverage flags
target_compile_options(Qt6MigratedBenchmark PRIVATE
-g
-O0
--coverage
-fprofile-arcs
-ftest-coverage
)
target_link_options(Qt6MigratedBenchmark PRIVATE
--coverage
-fprofile-arcs
-ftest-coverage
)
else()
message(WARNING "Coverage is only supported with GCC or Clang")
endif()
target_compile_features(Qt6MigratedBenchmark PRIVATE cxx_std_23)
# Enable warnings
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(Qt6MigratedBenchmark PRIVATE -Wall -Wextra -pedantic)
endif()
# Enable testing
# Add tests
enable_testing()
# Add test executable
add_executable(Qt6MigratedBenchmarkTest
tests/test_mainwindow.cpp
MainWindow.h
MainWindow.cpp
)
target_include_directories(Qt6MigratedBenchmarkTest PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(Qt6MigratedBenchmarkTest PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Test
)
# Apply same coverage flags to test target
if(ENABLE_COVERAGE)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(Qt6MigratedBenchmarkTest PRIVATE
-g
-O0
--coverage
-fprofile-arcs
-ftest-coverage
)
target_link_options(Qt6MigratedBenchmarkTest PRIVATE
--coverage
-fprofile-arcs
-ftest-coverage
)
endif()
endif()
# Register tests with CTest
add_test(NAME Qt6MigratedBenchmarkTest COMMAND Qt6MigratedBenchmarkTest)
add_subdirectory(tests)
@@ -1,241 +0,0 @@
# Code Coverage with LCOV
This document describes how to generate and view code coverage reports for the Qt6 migrated benchmark project.
## Prerequisites
### Local Development
Install the required tools:
```bash
# Ubuntu/Debian
sudo apt-get install lcov genhtml
# Fedora/RHEL
sudo dnf install lcov
# macOS (with Homebrew)
brew install lcov
```
### CI/CD Environment
The CI Docker image (`lifemaps/ci-cpp:latest`) includes `lcov` and `genhtml`. If you're building a custom CI image, ensure these packages are installed:
```dockerfile
RUN apt-get install -y --no-install-recommends \
lcov \
genhtml
```
## Generating Coverage Reports
### Local Coverage
1. **Navigate to the project directory:**
```bash
cd qt6-after/qt6-migrated-benchmark
```
2. **Run the coverage collection script:**
```bash
./scripts/collect-coverage.sh
```
Or specify a custom build directory:
```bash
./scripts/collect-coverage.sh /path/to/build-dir
```
3. **View the HTML report:**
```bash
xdg-open coverage/html/index.html # Linux
open coverage/html/index.html # macOS
```
### Manual Coverage Generation
If you prefer manual control:
```bash
# 1. Configure with coverage enabled
cmake -S qt6-after/qt6-migrated-benchmark -B build-coverage -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_COVERAGE=ON
# 2. Build the project
cmake --build build-coverage
# 3. Run tests to generate coverage data
ctest --test-dir build-coverage --output-on-failure
# 4. Collect coverage data
lcov --directory build-coverage \
--capture \
--output-file coverage.info \
--ignore-errors mismatch \
--no-external
# 5. Filter out system and Qt libraries
lcov --remove coverage.info \
'/usr/*' \
'/opt/*' \
'*/Qt6/*' \
'*/tests/*' \
--output-file coverage.filtered.info \
--ignore-errors mismatch
# 6. Generate HTML report
genhtml coverage.filtered.info \
--output-directory coverage/html \
--branch-coverage \
--ignore-errors mismatch \
--title "Qt6 Migration Benchmark Coverage"
```
## CI/CD Integration
### Jenkins Pipeline
The Jenkinsfile automatically runs coverage collection on `main`, `master`, or branches matching `coverage.*`. The coverage report is published using the Jenkins Coverage Plugin.
**Coverage Stage Configuration:**
- Runs only on protected branches
- Expects `coverage/lcov.info` at workspace root
- Publishes coverage trends across builds
**Viewing Coverage in Jenkins:**
1. Navigate to your job in Jenkins
2. Click on a build number
3. Find the "Coverage Report" section in the sidebar
4. Click to view detailed coverage statistics
### Coverage Report Location
After the Jenkins build completes:
- **LCOV data:** `coverage/lcov.info` (relative to workspace root)
- **HTML report:** Available in Jenkins UI via Coverage Plugin
## CMake Configuration
Coverage is controlled by the `ENABLE_COVERAGE` CMake option:
```cmake
option(ENABLE_COVERAGE "Enable code coverage with gcov/lcov" OFF)
```
### Coverage Flags
When enabled, the following flags are applied:
**Compile flags:**
- `-g` - Debug symbols
- `-O0` - No optimization
- `--coverage` - Enable coverage instrumentation
- `-fprofile-arcs` - Generate arc profiling data
- `-ftest-coverage` - Generate coverage data
**Link flags:**
- `--coverage` - Link with coverage libraries
- `-fprofile-arcs` - Link arc profiling support
## Test Requirements
Coverage reports are only as good as your test suite. Ensure you have:
1. **Unit tests** for individual functions and methods
2. **Integration tests** for component interactions
3. **Edge case coverage** for boundary conditions
4. **Error path coverage** for exception handling
### Running Tests
```bash
# Run all tests
ctest --test-dir build-coverage --output-on-failure
# Run specific test
ctest --test-dir build-coverage -R TestMainWindow --output-on-failure
# Run with verbose output
ctest --test-dir build-coverage -V
```
## Interpreting Coverage Results
### Coverage Metrics
- **Lines:** Percentage of executable lines executed
- **Functions:** Percentage of functions called
- **Branches:** Percentage of decision branches taken (if enabled)
### Coverage Targets
Aim for:
- **>80%** line coverage for production code
- **>70%** branch coverage for critical paths
- **100%** coverage for safety-critical modules
### Coverage Report Structure
The HTML report includes:
- **Summary table:** Overall coverage statistics
- **File list:** Per-file coverage breakdown
- **Source view:** Line-by-line coverage with highlighting
- Green: Executed lines
- Red: Never executed lines
- Yellow: Partially executed branches
## Troubleshooting
### No Coverage Data Generated
**Problem:** `lcov` reports "no valid records found"
**Solutions:**
1. Ensure tests actually run and complete successfully
2. Verify `ENABLE_COVERAGE=ON` was set during CMake configuration
3. Check that `.gcda` files are generated in the build directory
4. Ensure the same compiler is used for build and coverage collection
### Mismatch Errors
**Problem:** `lcov` reports "version mismatch"
**Solutions:**
1. Clean the build directory: `rm -rf build-coverage`
2. Reconfigure and rebuild with coverage enabled
3. Ensure consistent compiler versions
### Low Coverage Numbers
**Problem:** Coverage is lower than expected
**Solutions:**
1. Verify all tests are being executed (check CTest output)
2. Check if code is conditionally compiled out
3. Ensure test data exercises all code paths
4. Review filtered paths in `collect-coverage.sh`
## Best Practices
1. **Run coverage regularly** - Integrate into CI/CD pipeline
2. **Track trends** - Monitor coverage over time, not just absolute values
3. **Focus on critical paths** - Prioritize coverage of business logic
4. **Don't chase 100%** - Aim for meaningful coverage, not vanity metrics
5. **Review uncovered code** - Identify dead code or missing tests
## Related Files
- `CMakeLists.txt` - CMake configuration with coverage option
- `scripts/collect-coverage.sh` - Automated coverage collection script
- `Jenkinsfile` - CI/CD pipeline with coverage stage
- `ci/Dockerfile.ci` - Docker image with coverage tools
## References
- [LCOV Documentation](http://ltp.sourceforge.net/coverage/lcov.php)
- [CMake Testing with CTest](https://cmake.org/cmake/help/latest/manual/ctest.1.html)
- [GCC Coverage Instrumentation](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html)
- [Jenkins Coverage Plugin](https://plugins.jenkins.io/code-coverage-api/)
+18 -27
View File
@@ -4,20 +4,20 @@
#include <QApplication>
#include <QPixmap>
#include <QRect>
#include <QScreen>
#include <QString>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent), ui(new Ui::MainWindow), m_pattern(QRegularExpression(R"(^[A-Za-z]+\d+$)"))
: QMainWindow(parent), ui(new Ui::MainWindow), pattern(QRegularExpression(R"(^[A-Za-z]+\d+$)"))
{
ui->setupUi(this);
setWindowTitle(QStringLiteral("Qt6 Legacy Migration Benchmark"));
ui->label->setText(QStringLiteral("Bitte Text wie Test123 eingeben."));
ui->iconLabel->setPixmap(QPixmap(QStringLiteral(":/icons/app.png")).scaled(48, 48));
// Qt6 uses UTF-8 by default; no codec needed
setWindowTitle(QString::fromUtf8("Qt6 Migrated Benchmark"));
ui->label->setText(QString::fromUtf8("Bitte Text wie Test123 eingeben."));
ui->iconLabel->setPixmap(QPixmap(":/icons/app.png").scaled(48, 48));
// Modern function-pointer connect syntax
connect(ui->validateButton, &QPushButton::clicked, this, &MainWindow::onValidateClicked);
connect(ui->screenButton, &QPushButton::clicked, this, &MainWindow::onScreenInfoClicked);
}
@@ -27,38 +27,29 @@ MainWindow::~MainWindow()
delete ui;
}
bool MainWindow::validateInput(const QString& text) const
{
return m_pattern.match(text).hasMatch();
}
QString MainWindow::getScreenInfo() const
{
const QScreen* screen = QApplication::primaryScreen();
if (!screen)
{
return QStringLiteral("Screen: unavailable");
}
const QRect geometry = screen->geometry();
return QStringLiteral("Screen: %1x%2").arg(geometry.width()).arg(geometry.height());
}
void MainWindow::onValidateClicked()
{
const QString text = ui->lineEdit->text();
if (validateInput(text))
if (pattern.match(text).hasMatch())
{
ui->resultLabel->setText(QStringLiteral("Match"));
ui->resultLabel->setText(QString::fromUtf8("Match"));
}
else
{
ui->resultLabel->setText(QStringLiteral("No match"));
ui->resultLabel->setText(QString::fromUtf8("No match"));
}
}
void MainWindow::onScreenInfoClicked()
{
const QString info = getScreenInfo();
ui->resultLabel->setText(info);
// Qt6: Use QScreen instead of deprecated QDesktopWidget
const QScreen* screen = QApplication::primaryScreen();
if (screen)
{
const QRect screenGeometry = screen->geometry();
const QString info =
QString::fromUtf8("Screen: %1x%2").arg(screenGeometry.width()).arg(screenGeometry.height());
ui->resultLabel->setText(info);
}
}
@@ -2,6 +2,7 @@
#include <QMainWindow>
#include <QRegularExpression>
#include <QString>
QT_BEGIN_NAMESPACE
@@ -18,16 +19,13 @@ class MainWindow : public QMainWindow
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
~MainWindow() override;
[[nodiscard]] bool validateInput(const QString& text) const;
[[nodiscard]] QString getScreenInfo() const;
public slots:
private slots:
void onValidateClicked();
void onScreenInfoClicked();
private:
Ui::MainWindow* ui;
QRegularExpression m_pattern;
QRegularExpression pattern;
};
+12 -5
View File
@@ -1,12 +1,19 @@
#include "MainWindow.h"
#include <QApplication>
#include <QScreen>
int main(int argc, char* argv[])
#if __has_include(<print>)
#include <print>
#endif
[[nodiscard]] auto main(int argc, char* argv[]) -> int
{
QApplication a(argc, argv);
MainWindow w;
w.show();
QApplication app{argc, argv};
return a.exec();
// Qt6 uses UTF-8 by default; no codec setup needed
MainWindow window;
window.show();
return app.exec();
}
@@ -1,5 +1,5 @@
<RCC>
<qresource prefix="/icons">
<file>app.png</file>
</qresource>
</RCC>
<qresource prefix="/icons">
<file>app.png</file>
</qresource>
</RCC>
@@ -1,103 +0,0 @@
#!/bin/bash
#
# collect-coverage.sh - Generate LCOV coverage report for Qt6 project
#
# Usage: ./collect-coverage.sh [build_dir]
#
# This script:
# 1. Cleans previous coverage data
# 2. Runs tests with coverage enabled
# 3. Collects coverage data using lcov
# 4. Generates HTML report using genhtml
#
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
BUILD_DIR="${1:-${PROJECT_ROOT}/build-coverage}"
COVERAGE_DIR="${PROJECT_ROOT}/coverage"
echo "=== LCOV Coverage Collection ==="
echo "Project root: ${PROJECT_ROOT}"
echo "Build directory: ${BUILD_DIR}"
echo "Coverage output: ${COVERAGE_DIR}"
# Clean previous coverage data
echo ""
echo "Step 1: Cleaning previous coverage data..."
find "${BUILD_DIR}" -name "*.gcda" -delete 2>/dev/null || true
find "${BUILD_DIR}" -name "*.gcno" -delete 2>/dev/null || true
rm -rf "${COVERAGE_DIR}"
mkdir -p "${COVERAGE_DIR}"
# Configure with coverage enabled (if not already configured)
if [ ! -f "${BUILD_DIR}/CMakeCache.txt" ]; then
echo ""
echo "Step 2: Configuring project with coverage enabled..."
cmake -S "${PROJECT_ROOT}" -B "${BUILD_DIR}" -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_COVERAGE=ON \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
else
echo ""
echo "Step 2: Build directory already configured"
fi
# Build the project
echo ""
echo "Step 3: Building project..."
cmake --build "${BUILD_DIR}" --target all
# Run tests to generate coverage data
echo ""
echo "Step 4: Running tests to generate coverage data..."
ctest --test-dir "${BUILD_DIR}" --output-on-failure
# Collect coverage data
echo ""
echo "Step 5: Collecting coverage data with lcov..."
lcov --directory "${BUILD_DIR}" \
--capture \
--output-file "${COVERAGE_DIR}/coverage.info" \
--ignore-errors mismatch \
--no-external \
--rc lcov_branch_coverage=1
# Remove Qt and system libraries from coverage report
echo "Step 6: Filtering coverage data..."
lcov --remove "${COVERAGE_DIR}/coverage.info" \
'/usr/*' \
'/opt/*' \
'*/Qt6/*' \
'*/qt6-migrated-benchmark/tests/*' \
--output-file "${COVERAGE_DIR}/coverage.filtered.info" \
--ignore-errors mismatch,unused \
--rc lcov_branch_coverage=1
# Generate HTML report
echo ""
echo "Step 7: Generating HTML report with genhtml..."
genhtml "${COVERAGE_DIR}/coverage.filtered.info" \
--output-directory "${COVERAGE_DIR}/html" \
--branch-coverage \
--ignore-errors mismatch \
--title "Qt6 Migration Benchmark Coverage" \
--prefix "${PROJECT_ROOT}"
# Generate summary
echo ""
echo "=== Coverage Summary ==="
lcov --list "${COVERAGE_DIR}/coverage.filtered.info" \
--ignore-errors mismatch \
--rc lcov_branch_coverage=1
# Copy to expected Jenkins location
cp "${COVERAGE_DIR}/coverage.filtered.info" "${COVERAGE_DIR}/lcov.info"
echo ""
echo "=== Coverage Report Generated ==="
echo "HTML report: ${COVERAGE_DIR}/html/index.html"
echo "LCOV data: ${COVERAGE_DIR}/lcov.info (for Jenkins)"
echo ""
echo "To view the HTML report, open: ${COVERAGE_DIR}/html/index.html"
Whitespace-only changes.
@@ -0,0 +1,33 @@
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)
@@ -0,0 +1,75 @@
#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"
@@ -0,0 +1,63 @@
#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"
@@ -1,88 +0,0 @@
#include "MainWindow.h"
#include <QtTest/QtTest>
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 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 TestMainWindow::testWindowTitle()
{
MainWindow window;
QCOMPARE(window.windowTitle(), QStringLiteral("Qt6 Legacy Migration Benchmark"));
}
void TestMainWindow::testValidateInputMethod()
{
MainWindow window;
QVERIFY(window.validateInput(QStringLiteral("Test123")));
QVERIFY(window.validateInput(QStringLiteral("abc456")));
QVERIFY(!window.validateInput(QStringLiteral("123Test")));
QVERIFY(!window.validateInput(QStringLiteral("")));
}
void TestMainWindow::testGetScreenInfo()
{
MainWindow window;
QString info = window.getScreenInfo();
QVERIFY(!info.isEmpty());
QVERIFY(info.contains(QStringLiteral("Screen:")));
}
QTEST_MAIN(TestMainWindow)
#include "test_mainwindow.moc"
+1
View File
@@ -0,0 +1 @@
test