00 Kod kapsama nedir? — temel kavramlar
Kod kapsama (code coverage), testlerin kaynak kodun ne kadarını çalıştırdığını ölçen bir yazılım kalite metriğidir. Hangi satırların, dalların ve fonksiyonların test edilmediğini görünür kılar.
Kapsama türleri
| Kapsama türü | Ölçtüğü şey | Formül |
|---|---|---|
| Line coverage | Çalıştırılan kaynak satırları | çalışan satırlar / toplam satırlar |
| Branch coverage | if/else, switch dalları | alınan dallar / toplam dal sayısı |
| Function coverage | Çağrılan fonksiyonlar | çağrılan fonksiyon / toplam fonksiyon |
| Condition coverage | Boolean alt ifadeler | değerlendirilen koşullar / toplam koşullar |
gcov vs llvm-cov
gcov, GCC'nin yerleşik kapsama aracıdır. -fprofile-arcs -ftest-coverage (veya kısaca --coverage) bayraklarıyla derlenen binary'ler çalıştığında .gcda dosyaları üretir. gcov bu dosyaları okuyarak satır bazlı kapsama raporu oluşturur.
llvm-cov, Clang/LLVM'in eşdeğer aracıdır. Daha detaylı bölge kapsama (region coverage) bilgisi sunar ve JSON çıktısı üretebilir. Clang kullanan projelerde tercih edilir.
Embedded sistemlerde kapsama kısıtlamaları
Gömülü sistemlerde kapsama analizi birkaç önemli kısıtla gelir: (1) .gcda dosyaları yazılabilir bir filesystem gerektirir — ROM veya read-only rootfs'te çalışmaz; (2) dosya yazma yolu, cross-compile araç zincirine göre ayarlanmalıdır (GCOV_PREFIX); (3) kapsama enstrümantasyonu binary boyutunu ve yürütme hızını artırır — üretim image'larına dahil edilmemelidir.
01 Userspace gcov — gcc --coverage
Kullanıcı alanı C/C++ kodunda gcov kapsama verisi toplamak için --coverage derleyici bayrağı yeterlidir.
Derleme ve çalıştırma
# --coverage = -fprofile-arcs -ftest-coverage (hem derleme hem link)
gcc --coverage -O0 -g -o myprogram main.c sensor.c utils.c
# Programa özgü testleri çalıştır
./myprogram --test-mode
# Derleme sonrası üretilen dosyalar:
ls *.gc*
# main.gcno sensor.gcno utils.gcno ← derleme zamanı (kaynak→arc bilgisi)
# main.gcda sensor.gcda utils.gcda ← çalışma zamanı (sayım verisi)
gcno ve gcda dosyaları
Ham gcov raporu
# Tek dosya için kapsama raporu
gcov sensor.c
# Çıktı:
# File 'sensor.c'
# Lines executed:72.34% of 47
# Creating 'sensor.c.gcov'
# .gcov dosyasını oku
cat sensor.c.gcov
# -: 0:Source:sensor.c
# -: 1:#include "sensor.h"
# 1: 5:int sensor_init(sensor_t *s) {
# 1: 6: s->calibrated = 0;
# #####: 7: return -1; ← Bu satır hiç çalışmadı!
# -: 8:}
CMake entegrasyonu
cmake_minimum_required(VERSION 3.18)
project(myproject C)
option(ENABLE_COVERAGE "Kod kapsama enstrümantasyonu" OFF)
if(ENABLE_COVERAGE)
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
message(WARNING "Coverage için Debug build type önerilir")
endif()
target_compile_options(mylib PRIVATE --coverage -O0 -g)
target_link_options(mylib PRIVATE --coverage)
target_compile_options(tests PRIVATE --coverage -O0 -g)
target_link_options(tests PRIVATE --coverage)
endif()
# Kullanım:
# cmake -B build -DENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug
# cmake --build build
# cd build && ctest
# lcov --capture --directory . --output-file coverage.info
02 Cross-compile uyarıları — GCOV_PREFIX
Cross-compile ortamında gcov kullanmak, .gcda dosyalarının yazılacağı yolu açıkça belirlemeyi gerektirir. Aksi hâlde binary, host dosya sistemindeki derleme yoluna yazmaya çalışır.
Sorun: hardcoded derleme yolu
gcov enstrümantasyonu, .gcda dosyalarını derleme zamanındaki mutlak yola yazar. Arm çapraz derleyici ile /home/builder/project/src/sensor.c derlendiyse, hedef sistemde de bu yolu açmaya çalışır. Gömülü sistemde bu yol mevcut değildir ve .gcda dosyaları oluşturulamaz.
Çözüm: GCOV_PREFIX ve GCOV_PREFIX_STRIP
# DUT üzerinde (hedef sistemde):
# GCOV_PREFIX: .gcda dosyalarının yazılacağı yeni kök
# GCOV_PREFIX_STRIP: özgün yoldan baştan kaç bileşen silineceği
export GCOV_PREFIX=/tmp/coverage
export GCOV_PREFIX_STRIP=4
# Örnek: /home/builder/project/src/sensor.gcda
# Strip 4 → /project/src/sensor.gcda
# Prefix → /tmp/coverage/project/src/sensor.gcda
./myprogram --test-mode
# .gcda dosyaları artık /tmp/coverage/ altında
ls -R /tmp/coverage/
Cross-compile kapsama iş akışı
Host: cross-compile --coverage ile
│
▼
DUT: GCOV_PREFIX=/tmp/coverage ./myprogram
│ .gcda dosyaları /tmp/coverage/ altına yazılır
▼
Host: SCP ile .gcda dosyaları geri al
scp -r root@dut:/tmp/coverage ./build/
│
▼
Host: .gcno dosyaları ile birleştir
lcov --capture --directory ./build/ --output-file cov.info
│
▼
genhtml ile HTML rapor üret
.gcda sysroot stratejisi
# DUT'ta çalışır: testleri çalıştır ve .gcda'ları topla
ssh root@${DUT_IP} "
export GCOV_PREFIX=/tmp/cov
export GCOV_PREFIX_STRIP=3
/opt/myprogram/bin/myprogram --self-test
tar czf /tmp/cov.tar.gz /tmp/cov/
"
# Host'a aktar
scp root@${DUT_IP}:/tmp/cov.tar.gz ./
# Build dizinine çıkart (.gcno dosyaları ile aynı yerde olmalı)
tar xzf cov.tar.gz --strip-components=2 -C ./build/
# lcov ile birleştir
lcov --capture --directory ./build/ --output-file coverage.info \
--gcov-tool arm-linux-gnueabihf-gcov
03 lcov kullanımı — veri toplama ve işleme
lcov, gcov verilerini toplayan, filtreleyen ve genhtml için hazırlayan üst düzey bir araçtır. Karmaşık proje yapılarında gcov'u doğrudan kullanmaktan çok daha pratiktir.
Temel lcov iş akışı
# 1. Başlangıç (sıfır kapsama baseline) — isteğe bağlı
lcov --capture --initial \
--directory ./build \
--output-file baseline.info
# 2. Testleri çalıştır
./build/run_tests
# 3. Test sonrası kapsama verisi topla
lcov --capture \
--directory ./build \
--output-file test.info \
--gcov-tool gcov # cross-compile için: --gcov-tool arm-linux-gnueabihf-gcov
# 4. Baseline ile birleştir (isteğe bağlı, çalışmayan kodu da göster)
lcov --add-tracefile baseline.info \
--add-tracefile test.info \
--output-file combined.info
# 5. Filtrele: sistem ve üçüncü taraf başlıkları hariç tut
lcov --remove combined.info \
'/usr/*' \
'*/tests/*' \
'*/external/*' \
--output-file filtered.info
# 6. Özet yazdır
lcov --summary filtered.info
lcov --summary çıktısı
Reading tracefile filtered.info
Summary coverage rate:
lines......: 83.2% (1245 of 1496 lines)
functions..: 91.4% (96 of 105 functions)
branches...: 71.8% (447 of 622 branches)
genhtml ile HTML rapor üretme
genhtml filtered.info \
--output-directory coverage-report/ \
--title "MyProject Kod Kapsama Raporu" \
--legend \
--show-details \
--highlight \
--num-spaces 4
# Raporu aç
xdg-open coverage-report/index.html
# Web sunucusu üzerinden yayınla (CI artifact'larında)
python3 -m http.server 8080 --directory coverage-report/
lcov yapılandırma dosyası
# Otomatik filtre: test ve harici kod hariç
lcov_excl_pattern = .*_test\.c
lcov_excl_pattern = .*/external/.*
# Yüksek/orta/düşük kapsama eşikleri (HTML'de renklendirme)
genhtml_hi_limit = 90
genhtml_med_limit = 75
# Branch kapsama göster
lcov_branch_coverage = 1
# gcov aracı (cross-compile için özelleştir)
# gcov_tool = arm-linux-gnueabihf-gcov
04 HTML rapor okuma
genhtml'nin ürettiği HTML rapor, kapsanmayan kodu hızla bulmak için oldukça kullanışlıdır. Renk kodlaması ve metrikler bir bakışta neyin test edilmediğini gösterir.
Rapor yapısı
HTML rapor üç seviyeden oluşur: index.html tüm proje özetini ve dizin bazlı istatistikleri gösterir. Her dizine tıklandığında o dizindeki dosyaların listesi görülür. Dosya adına tıklandığında kaynak kod görünümü açılır.
Renk kodlaması
| Renk | Anlamı |
|---|---|
| Yeşil arka plan | Satır test tarafından çalıştırıldı |
| Kırmızı arka plan | Satır hiç çalıştırılmadı (ölü kod adayı) |
| Sarı/Turuncu | Kısmi branch coverage (if'in sadece bir kolu test edildi) |
| Mavi rakam | O satırın kaç kez çalıştırıldığı (hit count) |
Branch coverage görünümü
// gcov branch anotasyonu örneği:
// [ branch 0: 45 geçti, branch 1: 0 geçti ]
if (sensor->calibrated) { // ← sadece true kolu test edildi!
return sensor->read();
} else {
return -EINVAL; // ← bu kol hiç çalışmadı
}
Kapsanmayan kodu yorumlama
Kapsanmayan satırların her zaman test eksikliği anlamına gelmediğine dikkat edin. Bazı yaygın durumlar: hata işleme yolları (malloc başarısızlığı, NULL pointer — kasıtlı olarak test edilmeyebilir), platform özgü kod (#ifdef ile ayrılan dallar), ölü kod (gerçekten ulaşılamaz satırlar). lcov'un LCOV_EXCL_LINE ve LCOV_EXCL_START/STOP direktifleri ile bu tür satırlar rapordan hariç tutulabilir.
// Tek satırı hariç tut
void *buf = malloc(size);
if (!buf) return -ENOMEM; // LCOV_EXCL_LINE
// Bir bloğu hariç tut
// LCOV_EXCL_START
#ifdef DEBUG
dump_registers();
#endif
// LCOV_EXCL_STOP
05 Kernel gcov — CONFIG_GCOV_KERNEL
Linux kernel de gcov enstrümantasyonunu destekler. Kernel kodu kapsama analizi, sürücü test kalitesini değerlendirmek için değerli bilgi sağlar.
Kernel konfigürasyonu
CONFIG_GCOV_KERNEL=y
# Otomatik format algılama (önerilen — kernel versiyonuna göre)
CONFIG_GCOV_FORMAT_AUTODETECT=y
# Veya açıkça belirt:
# CONFIG_GCOV_FORMAT_GCOV4 — GCC 4.7-9
# CONFIG_GCOV_FORMAT_GCOV9 — GCC ≥ 10
# debugfs gerekli (gcov verisine erişim için)
CONFIG_DEBUG_FS=y
# Belirli bir modülü enstrümanlamak için:
# drivers/i2c/Makefile içine ekle:
# GCOV_PROFILE_i2c-core.o := y
# Veya tüm dizin için:
# GCOV_PROFILE := y
Kernel gcov verisi toplama
# debugfs'i mount et
mount -t debugfs nodev /sys/kernel/debug
# gcov dizini
ls /sys/kernel/debug/gcov/
# /sys/kernel/debug/gcov/proc/
# /sys/kernel/debug/gcov/drivers/
# /sys/kernel/debug/gcov/kernel/
# .gcda dosyaları burada yaşar (her modül/dosya için)
ls /sys/kernel/debug/gcov/drivers/i2c/
# i2c-core.gcda i2c-dev.gcda
# Tüm gcov verilerini kopyala
mkdir -p /tmp/kernel-cov
cp -r /sys/kernel/debug/gcov/* /tmp/kernel-cov/
Kernel kapsama raporu üretme
#!/bin/bash
# Kernel gcov raporu — host üzerinde çalıştır
KERNEL_SRC="/path/to/linux"
KERNEL_BUILD="/path/to/build"
GCDA_DIR="/tmp/kernel-cov"
# .gcda dosyalarını build dizinine kopyala
# (kernel gcov, .gcno dosyaları ile aynı yerde .gcda bekler)
find "$GCDA_DIR" -name "*.gcda" | while read f; do
rel="${f#$GCDA_DIR/}"
dest="$KERNEL_BUILD/$rel"
mkdir -p "$(dirname "$dest")"
cp "$f" "$dest"
done
# lcov ile veri topla
lcov --capture \
--directory "$KERNEL_BUILD" \
--output-file kernel-raw.info \
--gcov-tool gcov \
--rc lcov_branch_coverage=1
# Filtrele: yalnızca hedef sürücü
lcov --extract kernel-raw.info \
"*/drivers/i2c/*" \
--output-file i2c-coverage.info
# HTML rapor
genhtml i2c-coverage.info \
--output-directory kernel-coverage-html/ \
--title "Linux Kernel i2c Sürücü Kapsamı"
echo "Rapor: kernel-coverage-html/index.html"
gcov-tool kullanımı
# Birden fazla test çalıştırmasının .gcda verilerini birleştir
# (kernel modülü birden fazla test senaryosunda kullanıldıysa)
gcov-tool merge run1/ run2/ -o merged/
# gcov-tool özet
gcov-tool summary merged/ --format=json
06 CI kapsama kapısı — failure threshold
CI kapsama kapısı, kapsama oranı belirlenen eşiğin altına düştüğünde pipeline'ın başarısız olmasını sağlar. Bu mekanizma, kod tabanında kapsama regresyonunu önler.
lcov kapsama eşiği kontrolü
#!/bin/bash
# coverage-gate.sh — %80 eşik kontrolü
THRESHOLD=${1:-80}
INFO_FILE="${2:-filtered.info}"
# lcov summary'den yüzde değerini çek
COVERAGE=$(lcov --summary "$INFO_FILE" 2>&1 | \
grep "lines" | grep -oP '\d+\.\d+(?=%)' | head -1)
echo "Satır kapsamı: %${COVERAGE}"
echo "Eşik: %${THRESHOLD}"
# Python ile karşılaştır (bash float karşılaştırması güvensiz)
python3 - << EOF
coverage = float("${COVERAGE}")
threshold = float("${THRESHOLD}")
if coverage >= threshold:
print(f"PASS: %{coverage:.1f} >= %{threshold}")
exit(0)
else:
print(f"FAIL: %{coverage:.1f} < %{threshold}")
exit(1)
EOF
coverage.py XML ve GitLab kapsama badge
# lcov2cobertura ile Cobertura XML formatına dönüştür
pip install lcov-cobertura
lcov_cobertura filtered.info \
--base-dir src/ \
--output coverage.xml
# GitLab CI artifacts:reports:coverage_report için Cobertura formatı gerekir
GitLab kapsama regex ile badge
coverage-job:
script:
- lcov --summary filtered.info 2>&1 | tee coverage-summary.txt
# GitLab bu regex ile kapsama yüzdesini çeker ve badge gösterir
coverage: '/lines\.*:\s+(\d+\.\d+)%/'
07 Pratik: %80 kapsama hedefli GitLab CI pipeline
Embedded C kütüphanesi için sıfırdan kapsama altyapısı kurulumu: derleme, test çalıştırma, rapor üretme ve GitLab CI ile entegrasyon.
Proje yapısı
libsensor/
├── src/
│ ├── sensor.c
│ ├── sensor_cal.c
│ └── sensor_filter.c
├── include/
│ └── sensor.h
├── tests/
│ ├── test_sensor.c
│ ├── test_calibration.c
│ └── CMakeLists.txt
├── CMakeLists.txt
├── .lcovrc
└── .gitlab-ci.yml
CMakeLists.txt — kapsama desteği
cmake_minimum_required(VERSION 3.20)
project(libsensor C)
set(CMAKE_C_STANDARD 11)
add_library(sensor STATIC
src/sensor.c
src/sensor_cal.c
src/sensor_filter.c
)
target_include_directories(sensor PUBLIC include/)
# Kapsama seçeneği
option(COVERAGE "Kapsama enstrümantasyonu" OFF)
if(COVERAGE)
target_compile_options(sensor PUBLIC
--coverage -O0 -g --no-inline
)
target_link_options(sensor PUBLIC --coverage)
endif()
# Testler
enable_testing()
add_subdirectory(tests)
Tam .gitlab-ci.yml
image: debian:bookworm-slim
stages:
- build
- test
- coverage
- report
variables:
BUILD_DIR: "${CI_PROJECT_DIR}/build-cov"
COVERAGE_DIR: "${CI_PROJECT_DIR}/coverage"
THRESHOLD: "80"
before_script:
- apt-get update -qq
- apt-get install -y -qq gcc cmake lcov python3-pip git
- pip install lcov-cobertura -q
# ── Kapsama ile Derleme ──────────────────────────────
build-with-coverage:
stage: build
script:
- cmake -B ${BUILD_DIR}
-DCMAKE_BUILD_TYPE=Debug
-DCOVERAGE=ON
-DCMAKE_C_COMPILER=gcc
- cmake --build ${BUILD_DIR} -j$(nproc)
artifacts:
paths:
- ${BUILD_DIR}/
expire_in: 2h
# ── Testleri Çalıştır ────────────────────────────────
run-tests:
stage: test
needs: [build-with-coverage]
script:
- cd ${BUILD_DIR}
- ctest --output-on-failure -j$(nproc)
artifacts:
paths:
- ${BUILD_DIR}/
expire_in: 2h
# ── Kapsama Raporu Üret ──────────────────────────────
generate-coverage:
stage: coverage
needs: [run-tests]
script:
- mkdir -p ${COVERAGE_DIR}
# .gcda verilerini topla
- lcov --capture
--directory ${BUILD_DIR}
--output-file ${COVERAGE_DIR}/raw.info
--rc lcov_branch_coverage=1
# Filtrele: sadece kaynak kodu dahil et
- lcov --extract ${COVERAGE_DIR}/raw.info
"${CI_PROJECT_DIR}/src/*"
--output-file ${COVERAGE_DIR}/filtered.info
--rc lcov_branch_coverage=1
# Özet yazdır (GitLab badge için regex uyumlu)
- lcov --summary ${COVERAGE_DIR}/filtered.info
--rc lcov_branch_coverage=1 2>&1 | tee ${COVERAGE_DIR}/summary.txt
# HTML rapor
- genhtml ${COVERAGE_DIR}/filtered.info
--output-directory ${COVERAGE_DIR}/html/
--title "libsensor Kapsama"
--legend --show-details
--branch-coverage
# Cobertura XML (GitLab artifacts için)
- lcov_cobertura ${COVERAGE_DIR}/filtered.info
--base-dir src/
--output ${COVERAGE_DIR}/cobertura.xml
# Eşik kontrolü
- |
LINES=$(lcov --summary ${COVERAGE_DIR}/filtered.info 2>&1 | \
grep "lines" | grep -oP '\d+\.\d+(?=%)' | head -1)
echo "Satır kapsamı: ${LINES}% (eşik: ${THRESHOLD}%)"
python3 -c "
cov = float('${LINES}')
thr = float('${THRESHOLD}')
print(f'PASS: {cov:.1f}% >= {thr}%' if cov >= thr else f'FAIL: {cov:.1f}% < {thr}%')
import sys; sys.exit(0 if cov >= thr else 1)
"
coverage: '/lines\.*:\s+(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura.xml
paths:
- coverage/html/
- coverage/summary.txt
when: always
expire_in: 1 week
# ── Raporu Yayınla ────────────────────────────────────
pages:
stage: report
needs: [generate-coverage]
script:
- mkdir -p public
- cp -r ${COVERAGE_DIR}/html/ public/coverage/
artifacts:
paths:
- public/
only:
- main
Cross-compile için pipeline değişiklikleri
build-arm-coverage:
stage: build
image: registry.example.com/arm-toolchain:latest
script:
- cmake -B ${BUILD_DIR}
-DCMAKE_TOOLCHAIN_FILE=cmake/arm-toolchain.cmake
-DCOVERAGE=ON
-DCMAKE_BUILD_TYPE=Debug
- cmake --build ${BUILD_DIR} -j$(nproc)
run-arm-tests:
stage: test
tags: [arm-hil-board] # Gerçek ARM donanımında çalıştır
script:
- scp ${BUILD_DIR}/tests/test_sensor root@${DUT_IP}:/tmp/
- ssh root@${DUT_IP} "
export GCOV_PREFIX=/tmp/cov
export GCOV_PREFIX_STRIP=5
/tmp/test_sensor
tar czf /tmp/cov.tar.gz /tmp/cov/ 2>/dev/null || true
"
- scp root@${DUT_IP}:/tmp/cov.tar.gz ${BUILD_DIR}/
- tar xzf ${BUILD_DIR}/cov.tar.gz -C ${BUILD_DIR}/
generate-arm-coverage:
stage: coverage
script:
- lcov --capture
--directory ${BUILD_DIR}
--gcov-tool arm-linux-gnueabihf-gcov
--output-file ${COVERAGE_DIR}/raw.info
Kapsama enstrümantasyonu binary boyutunu %20-50 artırabilir ve çalışma hızını düşürebilir. Bu nedenle kapsama derlemesi her zaman ayrı bir build konfigürasyonu (-DCOVERAGE=ON) olarak tutulmalı ve üretim binary'lerine asla dahil edilmemelidir. CMake'de if(COVERAGE) bloğu bu ayrımı temiz biçimde sağlar.