From a6026137d16a1c1281ea6fee40fc79519d511b97 Mon Sep 17 00:00:00 2001 From: Andreas Hilbert Date: Thu, 2 Jul 2026 14:26:34 +0200 Subject: [PATCH 1/5] ci: add xccov->sonarqube coverage converter --- xccov-to-sonarqube-generic.sh | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 xccov-to-sonarqube-generic.sh diff --git a/xccov-to-sonarqube-generic.sh b/xccov-to-sonarqube-generic.sh new file mode 100644 index 0000000..bff2da3 --- /dev/null +++ b/xccov-to-sonarqube-generic.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +function convert_xccov_to_xml { + sed -n \ + -e '/:$/s/&/\&/g;s/^\(.*\):$/ /p' \ + -e 's/^ *\([0-9][0-9]*\): 0.*$/ /p' \ + -e 's/^ *\([0-9][0-9]*\): [1-9].*$/ /p' \ + -e 's/^$/ <\/file>/p' +} + +function xccov_to_generic { + local xcresult="$1" + + echo '' + # Strip $pwd (to make the file paths relative) + xcrun xccov view --archive "$xcresult" | while read -r line; do echo "${line#$PWD/}"; done | convert_xccov_to_xml + echo '' +} + +function check_xcode_version() { + local major=${1:-0} minor=${2:-0} + return $(( (major >= 14) || (major == 13 && minor >= 3) )) +} + +if ! xcode_version="$(xcodebuild -version | sed -n '1s/^Xcode \([0-9.]*\)$/\1/p')"; then + echo 'Failed to get Xcode version' 1>&2 + exit 1 +elif check_xcode_version ${xcode_version//./ }; then + echo "Xcode version '$xcode_version' not supported, version 13.3 or above is required" 1>&2; + exit 1 +fi + +xcresult="$1" +if [[ $# -ne 1 ]]; then + echo "Invalid number of arguments. Expecting 1 path matching '*.xcresult'" + exit 1 +elif [[ ! -d $xcresult ]]; then + echo "Path not found: $xcresult" 1>&2; + exit 1 +elif [[ $xcresult != *".xcresult"* ]]; then + echo "Expecting input to match '*.xcresult', got: $xcresult" 1>&2; + exit 1 +fi + +xccov_to_generic "$xcresult" \ No newline at end of file From e83133471445d248e7c17884f137ac298a3efa2f Mon Sep 17 00:00:00 2001 From: Andreas Hilbert Date: Thu, 2 Jul 2026 14:26:35 +0200 Subject: [PATCH 2/5] ci: add Unit Tests workflow with coverage artifact --- .github/workflows/unit_test.yml | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/unit_test.yml diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml new file mode 100644 index 0000000..0517a72 --- /dev/null +++ b/.github/workflows/unit_test.yml @@ -0,0 +1,45 @@ +name: Unit Tests + +on: + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: + +jobs: + test: + name: Run Xcode Tests + runs-on: macos-latest + env: + DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Build and Test + run: | + mkdir -p build/reports + xcodebuild test \ + -workspace Telegraph.xcworkspace \ + -scheme "Telegraph iOS" \ + -destination 'platform=iOS Simulator,name=iPad (10th generation)' \ + -enableCodeCoverage YES \ + -resultBundlePath build/TestResults.xcresult \ + ENABLE_TESTABILITY=YES \ + | xcpretty -c --report junit --output build/reports/junit.xml + + - name: Generate Code Coverage Report + run: | + mkdir -p build/reports + bash xccov-to-sonarqube-generic.sh build/TestResults.xcresult > build/reports/sonarqube-generic-coverage.xml + + - name: Upload Test Results + uses: actions/upload-artifact@v4 + with: + name: test-results + path: build/reports/ + retention-days: 7 From 210883c7299ad04c8c5c8c07c588a816bf0ff9d0 Mon Sep 17 00:00:00 2001 From: Andreas Hilbert Date: Thu, 2 Jul 2026 14:26:37 +0200 Subject: [PATCH 3/5] ci: add SonarQube scan (blueprint TillhubLocalServer-iOS) --- .github/workflows/sonarqube.yaml | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/sonarqube.yaml diff --git a/.github/workflows/sonarqube.yaml b/.github/workflows/sonarqube.yaml new file mode 100644 index 0000000..fece1ed --- /dev/null +++ b/.github/workflows/sonarqube.yaml @@ -0,0 +1,63 @@ +name: SonarQube +concurrency: sonarqube-check + +on: + workflow_run: + workflows: [Unit Tests] + types: [completed] + +jobs: + sonarqube-step: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: tillhub-org-runner + + steps: + - name: 'Download artifact' + uses: actions/github-script@v7 + with: + script: | + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + let matchArtifact = allArtifacts.data.artifacts.find(artifact => artifact.name === "test-results"); + if (!matchArtifact) { + throw new Error("Artifact 'test-results' not found"); + } + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + const fs = require('fs'); + const path = require('path'); + const temp = '${{ runner.temp }}/artifacts'; + if (!fs.existsSync(temp)){ + fs.mkdirSync(temp); + } + fs.writeFileSync(path.join(temp, 'test-results.zip'), Buffer.from(download.data)); + + - name: 'Unzip artifact' + run: | + unzip '${{ runner.temp }}/artifacts/test-results.zip' -d "${{ runner.temp }}/artifacts" + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: SonarQube Scan + uses: SonarSource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + with: + args: > + -Dsonar.projectKey=${{ github.event.repository.name }} + -Dsonar.sources=Sources + -Dsonar.tests=Tests + -Dsonar.inclusions=**/*.swift + -Dsonar.exclusions=**/*.h,**/*.m,**/*.mm,**/*.xib,**/*.storyboard,**/*.plist,**/*.strings,**/*.xcodeproj/**/*,**/*.xcworkspace/**/*,**/*.xcassets/**/*,**/*.xcscmblueprint,**/*.xccheckout,**/*.xcuserstate,**/*.xcworkspace,**/*.xcodeproj/project.pbxproj,**/*.xcodeproj/xcshareddata/**/*,**/*.xcodeproj/project.xcworkspace/**/*,**/*.xcodeproj/xcuserdata/**/*,**/*.xcuserdata/**/*,**/*Tests/**,**/*UITests/**,**/Pods/**,**/fastlane/**,**/Example/** + -Dsonar.coverageReportPaths=${{ runner.temp }}/artifacts/sonarqube-generic-coverage.xml From 74f13d36561f88cdc144d4b9c10f72f7b0e9bf88 Mon Sep 17 00:00:00 2001 From: Andreas Hilbert Date: Thu, 2 Jul 2026 14:32:46 +0200 Subject: [PATCH 4/5] ci: Debug config + dynamic simulator for testability/coverage --- .github/workflows/unit_test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 0517a72..7cbbaa7 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -23,10 +23,13 @@ jobs: - name: Build and Test run: | mkdir -p build/reports + DEST_ID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -oE "[0-9A-F-]{36}") + echo "Using simulator $DEST_ID" xcodebuild test \ -workspace Telegraph.xcworkspace \ -scheme "Telegraph iOS" \ - -destination 'platform=iOS Simulator,name=iPad (10th generation)' \ + -destination "platform=iOS Simulator,id=$DEST_ID" \ + -configuration Debug \ -enableCodeCoverage YES \ -resultBundlePath build/TestResults.xcresult \ ENABLE_TESTABILITY=YES \ From 9a4c3208dd0b679a65b6d08dad81e0e6ba197374 Mon Sep 17 00:00:00 2001 From: Andreas Hilbert Date: Thu, 2 Jul 2026 14:35:44 +0200 Subject: [PATCH 5/5] ci: run on macos-15 (stable Xcode 16 toolchain) --- .github/workflows/unit_test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 7cbbaa7..2e708a4 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -12,9 +12,7 @@ on: jobs: test: name: Run Xcode Tests - runs-on: macos-latest - env: - DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer + runs-on: macos-15 steps: - name: Checkout Repository