00 Yocto test ekosistemi — genel bakış
OpenEmbedded/Yocto, birbirini tamamlayan birden fazla test mekanizması sunar. Doğru araç seçimi, ne test etmek istediğinize bağlıdır.
Test mekanizmaları karşılaştırması
| Mekanizma | Ne test eder? | Ne zaman kullanılır? |
|---|---|---|
| testimage (oeqa runtime) | Çalışan image üzerinde sistem davranışı | Her image build sonrası otomatik smoke test |
| ptest | Bireysel paket işlevselliği | Upstream test paketleri (busybox, openssl, Python) |
| SDK test (oeqa sdk) | SDK toolchain ile derleme ve çalıştırma | SDK yayımı öncesi doğrulama |
| devtool test | Geliştirici iş akışı testleri | Yeni recipe entegrasyonu |
oeqa nedir?
oeqa (OpenEmbedded QA), OpenEmbedded'in test altyapısı paketidir. Python tabanlıdır ve üç ana test modülü içerir: oeqa.runtime (çalışan sistem testleri), oeqa.sdk (SDK testleri), oeqa.selftest (bitbake ve oeqa altyapısının kendi testi). Her modül, unittest tabanlı test case'ler içerir.
Akış diyagramı
bitbake core-image-minimal
│
▼
Image derleme tamamlanır
(.wic, .tar.bz2, kernel, dtb)
│
▼
bitbake -c testimage core-image-minimal
│
▼
oeqa test runner başlar
│
TEST_TARGET = qemu → QEMU başlatılır
TEST_TARGET = ssh → Gerçek donanıma SSH bağlantısı
│
▼
Test case'ler çalıştırılır
│
▼
Sonuçlar: tmp/log/oeqa/
01 testimage.bbclass — image testi
testimage.bbclass, bitbake üzerinden image test otomasyonu sağlar. Derleme sonrası otomatik test çalıştırmak için IMAGE_FEATURES üzerinden aktifleştirilir.
testimage'ı etkinleştirme
# testimage'ı etkinleştir
INHERIT += "testimage"
# Test hedefi: qemu (varsayılan) veya ssh
TEST_TARGET = "qemu"
# Çalıştırılacak test suite'leri
TEST_SUITES = "ping ssh df connman scp vnc date pam rpm ldd xorg syslog \
buildcpio buildlgl buildgalculator"
# QEMU için ağ köprüsü (isteğe bağlı)
# TEST_TARGET_IP = "192.168.7.2"
# Test zaman aşımı (saniye)
TEST_QEMUBOOT_TIMEOUT = "300"
# Sadece belirli test case'ler
# TEST_SUITES = "ping ssh"
testimage'ı çalıştırma
# Image derle + testimage'ı çalıştır
bitbake core-image-minimal -c testimage
# Yalnızca testimage (image zaten derlenmiş)
bitbake core-image-minimal -c testimage
# Belirli test sınıfını çalıştır
TEST_SUITES="ping ssh" bitbake -c testimage core-image-minimal
# Test sonuçlarını gör
cat tmp/log/oeqa/$(ls -t tmp/log/oeqa/ | head -1)/testresults.json | python3 -m json.tool
IMAGE_FEATURES ile test desteği ekleme
# ssh-server-openssh: SSH test için gerekli
IMAGE_FEATURES:append = " ssh-server-openssh"
# tools-debug: gdb, strace, ltrace testleri için
IMAGE_FEATURES:append = " tools-debug"
# ptest-pkgs: ptest paketleri dahil et
IMAGE_FEATURES:append = " ptest-pkgs"
# debug-tweaks: root şifresiz giriş (test ortamı için)
IMAGE_FEATURES:append = " debug-tweaks"
Test sonuçları konumu
02 QEMU üzerinde test çalıştırma
QEMU desteği Yocto'nun en güçlü özelliklerinden biridir. Gerçek donanım olmaksızın architecture-correct emülasyon ile kapsamlı sistem testleri yapılabilir.
runqemu ile manuel test
# ARM64 image çalıştır
runqemu qemuarm64 core-image-minimal
# Network erişimiyle (host → QEMU SSH)
runqemu qemuarm64 core-image-minimal nographic \
qemuparams="-m 512 -smp 2"
# Belirli kernel cmdline ekle
runqemu qemuarm64 core-image-minimal \
bootparams="console=ttyAMA0 panic=5"
# Tap ağ arayüzü (kök yetki gerekir)
runqemu qemuarm64 core-image-minimal slirp
# SSH ile bağlan
ssh root@192.168.7.2 # qemuarm64 varsayılan IP
QEMU makine yapılandırması
require conf/machine/include/qemu.inc
require conf/machine/include/arm/arch-arm64.inc
DEFAULTTUNE = "cortexa57"
KERNEL_IMAGETYPE = "Image"
MACHINE_FEATURES = "usbhost alsa screen"
# QEMU parametreleri
QB_SYSTEM_NAME = "qemu-system-aarch64"
QB_MACHINE = "-machine virt"
QB_CPU = "-cpu cortex-a57"
QB_DEFAULT_FSTYPE = "ext4"
QB_MEM = "-m 512"
QB_OPT_APPEND = "-nographic -no-reboot"
# Ağ
QB_NETWORK_DEVICE = "-device virtio-net-device,netdev=net0,mac=@MAC@"
QB_TAP_OPT = "-netdev tap,id=net0,ifname=@TAP@,script=no,downscript=no"
QB_SLIRP_OPT = "-netdev user,id=net0"
QEMU testi hızlandırma
QEMU üzerinde testimage çalıştırmak uzun sürebilir. Birkaç optimizasyon tekniği: KVM (host x86 ise QEMU x86 görüntüsü ile 5-10x hızlanma), tmpfs üzerinde build (disk I/O azaltma), sstate cache kullanımı (yeniden derlemeyi önler), paralel test çalıştırma birden fazla QEMU instance'ı ile.
# KVM hızlandırma (x86 host + x86 target için)
QB_OPT_APPEND:append:qemux86-64 = " -enable-kvm"
# Daha fazla bellek (testler için)
QB_MEM = "-m 1024"
# Çok çekirdek
QB_CPU_KVM = "-cpu host -smp 4"
# sstate cache (CI'da önemli)
SSTATE_DIR = "/shared/sstate-cache"
DL_DIR = "/shared/downloads"
03 ptest çerçevesi — paket bazlı test
ptest (package test), bireysel paketlerin kendi test paketlerini hedef cihaz üzerinde çalıştırmasını sağlayan Yocto standardıdır. Upstream test paketleri (ör. busybox-ptest, openssl-ptest) bu mekanizma ile kullanılır.
ptest nasıl çalışır?
Recipe: PACKAGES:append = " ${PN}-ptest"
│
▼
do_install_ptest_base() çalışır
Test dosyaları: ${D}${PTEST_PATH}/
│
▼
Image içine dahil edilir
/usr/lib/${PN}/ptest/ dizinine
│
▼
DUT üzerinde ptest-runner çalıştırılır
ptest-runner -d /usr/lib/
│
▼
Sonuçlar stdout'a yazılır:
PASS: test-name
FAIL: test-name
ptest destekli recipe yazımı
SUMMARY = "Örnek C kütüphanesi"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=abc123"
SRC_URI = "git://github.com/example/mylib.git;protocol=https;branch=main"
SRCREV = "abc123..."
S = "${WORKDIR}/git"
inherit cmake ptest
# ptest desteği için DEPENDS ekle
DEPENDS = "check" # unit test framework
do_compile_ptest() {
# Test binary'lerini derle
oe_runmake -C ${B} tests
}
do_install_ptest() {
# Test binary ve script'lerini ptest dizinine kopyala
install -d ${D}${PTEST_PATH}
install -m 0755 ${B}/tests/test_mylib ${D}${PTEST_PATH}/
install -m 0755 ${S}/tests/run-ptest ${D}${PTEST_PATH}/
}
# ptest runtime bağımlılıkları
RDEPENDS:${PN}-ptest += "bash ptest-runner"
run-ptest scripti
#!/bin/sh
# ptest-runner bu scripti çalıştırır
# Çıktı formatı: "PASS: test-adı" veya "FAIL: test-adı"
PTEST_DIR=$(dirname "$0")
run_test() {
local name="$1"
local cmd="$2"
if $cmd > /dev/null 2>&1; then
echo "PASS: $name"
else
echo "FAIL: $name"
fi
}
run_test "basic-init" "${PTEST_DIR}/test_mylib --test basic"
run_test "memory-alloc" "${PTEST_DIR}/test_mylib --test memory"
run_test "thread-safety" "${PTEST_DIR}/test_mylib --test threads"
run_test "edge-cases" "${PTEST_DIR}/test_mylib --test edge"
ptest'i image'a dahil etme
# ptest-pkgs feature tüm ptest paketlerini dahil eder
IMAGE_FEATURES:append = " ptest-pkgs"
# Veya belirli paketlerin ptest versiyonunu seç
IMAGE_INSTALL:append = " mylib-ptest busybox-ptest openssl-ptest python3-ptest"
# ptest-runner aracı (ptest çalıştırıcı)
IMAGE_INSTALL:append = " ptest-runner"
04 Runtime test yazma — OERuntimeTestCase
OERuntimeTestCase, oeqa runtime test case'lerini yazmak için temel sınıftır. SSH üzerinden DUT'a komut gönderir, çıktıyı alır ve assert ile doğrular.
Temel OERuntimeTestCase yapısı
from oeqa.runtime.case import OERuntimeTestCase
from oeqa.core.decorator.depends import OETestDepends
from oeqa.core.decorator.data import OETestDataDepends
class MyCustomTest(OERuntimeTestCase):
"""Özel runtime test case örneği."""
@OETestDepends(['ssh.SSHTest.test_ssh'])
def test_kernel_version(self):
"""Kernel sürümünün beklenen major version'a sahip olduğunu doğrula."""
status, output = self.target.run("uname -r")
self.assertEqual(status, 0, "uname komutu başarısız")
major = int(output.split(".")[0])
self.assertGreaterEqual(major, 6, f"Kernel 6.x beklendi, alınan: {output}")
@OETestDepends(['ssh.SSHTest.test_ssh'])
def test_ethernet_up(self):
"""Ethernet arayüzünün aktif olduğunu doğrula."""
status, output = self.target.run("ip link show eth0")
self.assertEqual(status, 0, "eth0 bulunamadı")
self.assertIn("UP", output, f"eth0 aktif değil: {output}")
def test_disk_space(self):
"""Root partition boş alan kontrolü."""
status, output = self.target.run(
"df / | tail -1 | awk '{print $5}' | tr -d '%'"
)
self.assertEqual(status, 0)
usage = int(output.strip())
self.assertLess(usage, 90, f"Root partition dolu: %{usage}")
@OETestDepends(['ssh.SSHTest.test_ssh'])
def test_file_copy(self):
"""SCP ile dosya kopyalama testi."""
# Host'tan DUT'a dosya gönder
self.target.copy_to("/tmp/test-file.txt", "/tmp/test-file.txt")
status, output = self.target.run("cat /tmp/test-file.txt")
self.assertEqual(status, 0)
# Temizlik
self.target.run("rm /tmp/test-file.txt")
Test suite kaydı
# Özel test modülü yolunu ekle
TEST_SUITES_APPEND = "mytest"
# TEST_SUITES içinde kullanmak için tam modül yolu
TEST_SUITES = "ping ssh df mytest"
# Özel test dosyasının bulunduğu layer'ı ekle
BBPATH .= ":${LAYERDIR}"
# Test dosyaları araştırma yolu
OEQA_CUSTOM_TESTS_DIR = "${LAYERDIR}/lib/oeqa/runtime/cases"
SSH executor yapılandırması
# Gerçek donanıma SSH ile test
TEST_TARGET = "ssh"
TEST_TARGET_IP = "192.168.1.50"
TEST_SERVER_IP = "192.168.1.10" # Host IP (dosya transferi için)
# SSH key (root şifresiz erişim)
TEST_TARGET_SSH_OPTIONS = "-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-i /home/builder/.ssh/id_rsa_test"
05 SDK test — oeqa.sdk
oeqa.sdk, oluşturulan SDK'nın (Software Development Kit) düzgün çalışıp çalışmadığını doğrular. Özellikle SDK yayımı öncesinde kritik önem taşır.
SDK oluşturma
# Standart SDK
bitbake core-image-minimal -c populate_sdk
# eSDK (extensible SDK)
bitbake core-image-minimal -c populate_sdk_ext
# SDK çıktı konumu
ls tmp/deploy/sdk/
# poky-glibc-x86_64-core-image-minimal-cortexa57-toolchain-4.3.sh
SDK testini çalıştırma
# SDK testini çalıştır
bitbake core-image-minimal -c testsdk
# local.conf içinde test suite ayarı
TOOLCHAIN_TEST_HOST = "192.168.1.5"
TOOLCHAIN_TEST_HOST_USER = "root"
TOOLCHAIN_TEST_HOST_PASSWORD = ""
# SDK test suite seçimi
SDK_TARGET_MANIFEST = ""
Özel SDK test case
import os
import subprocess
from oeqa.sdk.case import OESDKTestCase
class MySDKBuildTest(OESDKTestCase):
"""SDK ile C programı derleme ve çalıştırma testi."""
tc_is_sdk = True
def test_c_compile(self):
"""Basit bir C programını SDK ile derle."""
src = """
#include
int main(void) {
printf("SDK Build Test OK\\n");
return 0;
}
"""
src_path = os.path.join(self.tc.sdk_dir, "test_sdk.c")
bin_path = os.path.join(self.tc.sdk_dir, "test_sdk")
with open(src_path, "w") as f:
f.write(src)
# SDK environment'ı source edip derle
result = self._run(
f"${{CC}} {src_path} -o {bin_path} && echo COMPILE_OK"
)
self.assertIn("COMPILE_OK", result)
def test_c_run(self):
"""Derlenen binary'yi çalıştır."""
bin_path = os.path.join(self.tc.sdk_dir, "test_sdk")
result = self._run(f"{bin_path}")
self.assertIn("SDK Build Test OK", result)
def test_pkg_config(self):
"""pkg-config ile kütüphane sorgulama."""
result = self._run("pkg-config --libs openssl 2>&1 || echo MISSING")
# openssl kütüphanesi SDK içinde mevcut olmalı
self.assertNotIn("MISSING", result)
06 wic image + hardware test
wic ile oluşturulan SD kart imajları gerçek donanıma yazdırılarak test edilebilir. LAVA entegrasyonu veya script tabanlı flash + SSH test akışı bu senaryoyu otomatize eder.
wic image oluşturma
# wic image derle
bitbake core-image-minimal
# Çıktı
ls tmp/deploy/images/raspberrypi4-64/
# core-image-minimal-raspberrypi4-64.rootfs.wic.bz2
# Sıkıştırmayı aç
bunzip2 core-image-minimal-raspberrypi4-64.rootfs.wic.bz2
Otomatik SD kart flash scripti
#!/bin/bash
# flash-and-test.sh — wic image yaz ve test çalıştır
set -e
IMAGE="$1"
SD_DEVICE="${2:-/dev/sdb}"
DUT_IP="${3:-192.168.1.50}"
echo "[1/4] SD kart yazılıyor: $SD_DEVICE"
dd if="$IMAGE" of="$SD_DEVICE" bs=4M conv=fsync status=progress
sync
echo "[2/4] DUT güç döngüsü..."
/usr/local/bin/pdu-ctrl.sh cycle dut-01
echo "[3/4] SSH erişimi bekleniyor..."
TIMEOUT=120
ELAPSED=0
until ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 \
root@$DUT_IP "echo ALIVE" 2>/dev/null; do
sleep 5
ELAPSED=$((ELAPSED + 5))
[ $ELAPSED -ge $TIMEOUT ] && { echo "SSH timeout!"; exit 1; }
done
echo "[4/4] Testler çalıştırılıyor..."
pytest tests/post-flash/ \
--target-ip=$DUT_IP \
--junit-xml=reports/flash-test.xml
echo "Flash ve test tamamlandı."
LAVA entegrasyonu ile wic flash
job_name: yocto-wic-flash-test
device_type: rpi4b
priority: medium
actions:
- deploy:
to: usb
image:
url: http://artifact-server/core-image-minimal.wic.bz2
compression: bz2
os: oe
- boot:
method: u-boot
commands: default
auto_login:
login_prompt: "login:"
username: root
prompts:
- "root@raspberrypi4:~#"
- test:
definitions:
- from: inline
name: post-flash-smoke
path: inline/smoke.yaml
repository:
metadata:
format: Lava-Test Test Definition 1.0
name: smoke
run:
steps:
- lava-test-case "boot-ok" --result pass
- lava-test-case "storage" --result pass \
--measurement "$(df / | tail -1 | awk '{print $4}')" --units KB
07 Pratik: Custom ptest recipe + GitLab CI pipeline
Sıfırdan bir ptest recipe oluşturma ve bunu GitLab CI içinde otomatik olarak derleme, test ve raporlama adımlarına entegre etme.
Örnek C kütüphanesi ve ptest recipe
SUMMARY = "Sensor okuma kütüphanesi"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=abc123"
SRC_URI = "git://gitlab.example.com/firmware/libsensor.git;protocol=https;branch=main"
SRCREV = "${AUTOREV}"
PV = "1.0+git${SRCPV}"
S = "${WORKDIR}/git"
inherit cmake ptest
DEPENDS = "libcheck"
# Normal derleme
do_configure:prepend() {
cmake_do_configure
}
# ptest derleme
do_compile_ptest() {
oe_runmake -C ${B} sensor_tests
}
# ptest kurulum
do_install_ptest() {
install -d ${D}${PTEST_PATH}
install -m 0755 ${B}/tests/sensor_tests ${D}${PTEST_PATH}/
install -m 0755 ${S}/tests/run-ptest ${D}${PTEST_PATH}/
# Test fixtures (örnek veri dosyaları)
install -d ${D}${PTEST_PATH}/fixtures
cp -r ${S}/tests/fixtures/ ${D}${PTEST_PATH}/fixtures/
}
RDEPENDS:${PN}-ptest += "bash ptest-runner libcheck"
FILES:${PN}-ptest += "${PTEST_PATH}"
Tam GitLab CI pipeline
stages:
- build-image
- test-qemu
- test-hardware
variables:
MACHINE: "qemuarm64"
DISTRO: "poky"
IMAGE: "core-image-minimal"
BUILD_DIR: "${CI_PROJECT_DIR}/build"
SSTATE_DIR: "/shared/yocto-sstate"
DL_DIR: "/shared/yocto-downloads"
# ── Image Derleme ───────────────────────────────────
build-yocto-image:
stage: build-image
tags: [yocto-builder]
image: crops/poky:ubuntu-22.04
script:
- source poky/oe-init-build-env ${BUILD_DIR}
- |
cat >> conf/local.conf << 'EOF'
MACHINE = "${MACHINE}"
DISTRO = "${DISTRO}"
SSTATE_DIR = "${SSTATE_DIR}"
DL_DIR = "${DL_DIR}"
INHERIT += "testimage"
IMAGE_FEATURES:append = " debug-tweaks ssh-server-openssh ptest-pkgs"
IMAGE_INSTALL:append = " libsensor-ptest"
EOF
- bitbake ${IMAGE}
artifacts:
paths:
- build/tmp/deploy/images/${MACHINE}/
- build/tmp/deploy/sdk/
expire_in: 1 day
# ── QEMU Runtime Testleri ───────────────────────────
testimage-qemu:
stage: test-qemu
tags: [yocto-builder]
needs: [build-yocto-image]
image: crops/poky:ubuntu-22.04
script:
- source poky/oe-init-build-env ${BUILD_DIR}
- |
cat >> conf/local.conf << 'EOF'
TEST_TARGET = "qemu"
TEST_SUITES = "ping ssh df libsensor"
QB_MEM = "-m 512"
EOF
- bitbake ${IMAGE} -c testimage
- |
# Sonuçları JUnit'e dönüştür
python3 scripts/oeqa-to-junit.py \
build/tmp/log/oeqa/ \
reports/qemu-junit.xml
artifacts:
reports:
junit: reports/qemu-junit.xml
paths:
- build/tmp/log/oeqa/
when: always
# ── Hardware Testleri (SSH hedef) ───────────────────
testimage-hardware:
stage: test-hardware
tags: [hil-yocto-board]
needs: [build-yocto-image]
resource_group: rpi4-board-01
variables:
DUT_IP: "192.168.1.50"
script:
- source poky/oe-init-build-env ${BUILD_DIR}
- |
cat >> conf/local.conf << 'EOF'
TEST_TARGET = "ssh"
TEST_TARGET_IP = "${DUT_IP}"
TEST_SUITES = "ping ssh df libsensor ptest"
EOF
# wic image'ı flash et
- ../scripts/flash-and-test.sh \
build/tmp/deploy/images/raspberrypi4-64/core-image-minimal.wic.bz2 \
/dev/sdb ${DUT_IP}
- bitbake ${IMAGE} -c testimage
artifacts:
reports:
junit: reports/hw-junit.xml
when: always
Sonuç raporlama scripti
#!/usr/bin/env python3
"""oeqa testresults.json → JUnit XML dönüştürücü."""
import json
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
from datetime import datetime
def convert(oeqa_dir: Path, output_file: Path):
# En son test sonuç dizinini bul
results_files = list(oeqa_dir.glob("*/testresults.json"))
if not results_files:
print("testresults.json bulunamadı")
sys.exit(1)
latest = max(results_files, key=lambda p: p.stat().st_mtime)
data = json.loads(latest.read_text())
suite = ET.Element("testsuite",
name="oeqa",
timestamp=datetime.now().isoformat()
)
for test_name, result in data.items():
case = ET.SubElement(suite, "testcase", name=test_name)
if result["status"] == "FAILED":
failure = ET.SubElement(case, "failure", message=result.get("log", ""))
failure.text = result.get("log", "")
elif result["status"] == "ERROR":
error = ET.SubElement(case, "error", message=result.get("log", ""))
tree = ET.ElementTree(suite)
output_file.parent.mkdir(parents=True, exist_ok=True)
tree.write(output_file, encoding="utf-8", xml_declaration=True)
print(f"JUnit rapor yazıldı: {output_file}")
if __name__ == "__main__":
convert(Path(sys.argv[1]), Path(sys.argv[2]))
Yocto derlemeleri uzun sürer. CI'da sstate cache paylaşımı çok önemlidir. SSTATE_DIR ve DL_DIR'i pipeline'lar arasında paylaşılan bir NFS/S3 dizinine yönlendirin. Bu ayar yeniden derleme süresini genellikle %80-90 oranında azaltır.