diff --git a/.fmf/version b/.fmf/version new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/.fmf/version @@ -0,0 +1 @@ +1 diff --git a/gating.yaml b/gating.yaml index d353613..f707dc8 100644 --- a/gating.yaml +++ b/gating.yaml @@ -4,3 +4,4 @@ product_versions: decision_context: osci_compose_gate rules: - !PassingTestCaseRule {test_case_name: desktop-qe.desktop-ci.tier1-gating.functional} + - !PassingTestCaseRule {test_case_name: osci.brew-build.tier0.functional} diff --git a/plan.fmf b/plan.fmf new file mode 100644 index 0000000..4604c6a --- /dev/null +++ b/plan.fmf @@ -0,0 +1,108 @@ +/plan-graphics-sanity-test: + summary: Graphics stack smoke test plan + discover: + how: fmf + provision: + how: local + prepare: + how: shell + script: | + set -x # Enable verbose logging + echo "========================================" + echo "Installing desktop environment and graphics dependencies" + echo "Architecture: $(uname -m)" + echo "OS: $(cat /etc/redhat-release)" + echo "========================================" + + # Install full GNOME desktop environment (minimal) + echo "Step 1: Installing GNOME desktop environment..." + dnf groupinstall -y "Server with GUI" || \ + dnf groupinstall -y "GNOME Desktop" || \ + dnf install -y @gnome-desktop + + # Install Wayland session specifically + echo "Step 2: Installing Wayland session components..." + dnf install -y \ + gnome-session-wayland-session \ + gnome-shell \ + mutter \ + xorg-x11-server-Xwayland + + # Install graphics stack dependencies + echo "Step 3: Installing graphics stack dependencies..." + dnf install -y \ + mesa-dri-drivers \ + mesa-libGL \ + mesa-libEGL \ + mesa-libGLES \ + mesa-vulkan-drivers \ + gtk3 \ + gtk4 \ + xwayland-run \ + mesa-demos \ + python3-gobject \ + glx-utils + + # Install optional packages + echo "Step 4: Installing optional packages..." + dnf install -y gtk3-devel gtk4-devel xterm glxgears || true + + # Verify key components + echo "Step 5: Verifying installation..." + echo " mutter: $(which mutter || echo 'NOT FOUND')" + echo " xwfb-run: $(which xwfb-run || echo 'NOT FOUND')" + echo " glxinfo: $(which glxinfo || echo 'NOT FOUND')" + echo " Software renderer: $(ls /usr/lib64/dri/swrast_dri.so 2>/dev/null || echo 'NOT FOUND')" + + echo "========================================" + echo "Installation complete" + echo "========================================" + execute: + how: tmt + +/tests: + /infrastructure-test: + summary: Graphics infrastructure test (Phases 1-3) + description: | + Validates graphics stack infrastructure: + - Phase 1: System info and prerequisites + - Phase 2: Library initialization (Mesa, GTK) + - Phase 3: Headless compositor and GL context + + This test verifies that the graphics stack is properly set up + before running actual rendering tests. + test: SKIP_INSTALL=1 ./tests/graphics-smoke-test-tmt.sh --phase=1-3 + tty: true + order: 10 + duration: 4m + contact: + - Peter Kopec + component: + - mesa + - gtk3 + - mutter + - xwayland + + /rendering-test: + summary: Graphics rendering verification (Phase 4) + description: | + Validates actual graphics rendering with visual verification: + - GTK3/GTK4 application startup + - GTK4 rendering test with default renderer + - GTK4 rendering test with NGL renderer + - Visual verification via PNG color analysis + - glxgears FPS test + + Detects the s390x Mesa 25.2.7-3 black screen bug where + GTK4 apps show black windows with default renderer but + work correctly with GSK_RENDERER=ngl. + test: SKIP_INSTALL=1 ./tests/graphics-smoke-test-tmt.sh --phase=4 + tty: true + order: 20 + duration: 8m + contact: + - Peter Kopec + component: + - mesa + - gtk4 + - mutter diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..9e0a469 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,321 @@ +# Universal Mesa Graphics Smoke Test + +Multi-architecture headless graphics stack testing for Mesa, GTK3/GTK4, and the GNOME compositor. + +## Overview + +This test suite validates the graphics stack functionality across **4 CPU architectures**: +- **x86_64** (Intel/AMD) +- **aarch64** (ARM 64-bit) +- **ppc64le** (PowerPC Little-Endian) +- **s390x** (IBM Z System) + +The tests run entirely **headless** (no physical display required) using `xwfb-run` (Xwayland framebuffer) for testing on bare metal, VMs, or CI infrastructure. + +## What It Tests + +### Phase 1: System Information & Prerequisites +- Architecture and kernel information +- CPU, memory, and hardware details +- Required package installation verification: + - `mesa-dri-drivers` + - `gtk3`, `gtk4` + - `mutter` (Wayland compositor) + - `xwayland-run` (headless testing framework) + - Mesa GL/EGL/Vulkan drivers + +### Phase 2: Graphics Library Initialization +- Mesa GL library loading (`libGL.so.1`) +- GTK3/GTK4 Python bindings import test +- Software renderer availability check (llvmpipe/swrast) + +### Phase 3: Headless Graphics Stack +- Wayland compositor startup (`xwfb-run`) +- X11/Xwayland compatibility layer +- OpenGL context creation +- Renderer detection (`glxinfo`) + - Reports vendor, renderer, and GL version + +### Phase 4: Application Rendering Tests +- **GTK3 Demo**: Application startup and rendering + - `gtk3-demo --list` (enumerate demos) + - `gtk3-demo --run=textscroll --autoquit` (actual demo execution) + +- **GTK4 Demo**: Application startup and rendering + - `gtk4-demo --list` (enumerate demos) + - `gtk4-demo --run=textscroll --autoquit` (actual demo execution) + +- **GTK4 Visual Verification** (detects rendering bugs): + - Renders colored shapes (red, green, blue rectangles + yellow circle) + - Saves PNG screenshot + - Analyzes color diversity to detect black screen bug + - Tests **both** default renderer and NGL renderer + +- **glxgears**: OpenGL performance test + - Runs for 25 seconds + - Verifies FPS output and GL rendering + +## Known Issues Detected + +### s390x Mesa 25.2.7-3 Black Screen Bug +**Symptom**: GTK4 applications display black windows with the default renderer. + +**Detection**: The visual verification test counts unique colors in the rendered PNG. A properly rendered image has 50+ unique color values; a black screen has <10. + +**Workaround**: Setting `GSK_RENDERER=ngl` forces the NGL (new GL) renderer and fixes the issue. + +This test suite automatically detects this failure and tests the NGL workaround. + +## Architecture + +``` +universal_sanity_test/ +├── main.fmf # TMT test definition (Testing Farm) +├── graphics-smoke-test-rhts.sh # Main test script with RHTS reporting +├── gtk4_render_test.py # GTK4 visual verification app +└── README.md # This file +``` + +### Test Framework: TMT (Test Management Tool) + +The `main.fmf` file defines: +- **Plan**: `plan-graphics-sanity-test` + - Provisioning: `local` (runs on current system) + - Preparation: Installs GNOME desktop and graphics dependencies + - Execution: TMT test runner + +- **Test 1**: `infrastructure-test` (Phases 1-3) + - Duration: 4 minutes + - Order: 10 (runs first) + - Tags: `CI-Tier-1`, `graphics`, `infrastructure`, `multiarch` + +- **Test 2**: `rendering-test` (Phase 4) + - Duration: 8 minutes + - Order: 20 (runs second) + - Tags: `CI-Tier-1`, `graphics`, `rendering`, `visual-verification`, `multiarch` + +### RHTS Reporting + +All test results are reported via `tmt-report-result` for integration with: +- Testing Farm +- RHEL Testing Infrastructure +- CI/CD pipelines + +Each test reports one of: +- **PASS**: Test succeeded +- **FAIL**: Critical test failed +- **WARN**: Optional test failed (doesn't affect overall result) +- **SKIP**: Test was skipped (dependency not available) + +## Usage + +### Run All Tests (Recommended) +```bash +./graphics-smoke-test-rhts.sh +``` + +### Run Only Infrastructure Tests (Phases 1-3) +```bash +./graphics-smoke-test-rhts.sh --phase=1-3 +``` + +### Run Only Rendering Tests (Phase 4) +```bash +./graphics-smoke-test-rhts.sh --phase=4 +``` + +### Run with TMT (Testing Farm) +```bash +tmt run --all +``` + +### Run Specific TMT Test +```bash +# Infrastructure only +tmt run test --name /tests/infrastructure-test + +# Rendering only +tmt run test --name /tests/rendering-test +``` + +## Requirements + +### System Requirements +- RHEL 9+, Fedora 35+, or CentOS Stream 9+ +- Supported architectures: x86_64, aarch64, ppc64le, s390x +- No physical display required (fully headless) + +### Required Packages +The test automatically installs these if missing: +- `mesa-dri-drivers` - Mesa DRI drivers (required) +- `mesa-libGL`, `mesa-libEGL`, `mesa-libGLES` - Mesa GL libraries +- `mesa-vulkan-drivers` - Mesa Vulkan support +- `gtk3`, `gtk4` - GTK widget toolkit +- `xwayland-run` - Headless X server wrapper +- `mutter` - GNOME Wayland compositor +- `gnome-session-wayland-session` - Wayland session components +- `mesa-demos` - GL utilities (glxinfo, glxgears) +- `python3-gobject` - Python GTK bindings + +### Optional Packages +- `gtk3-devel`, `gtk4-devel` - Development tools +- `glx-utils` - GLX utilities +- `xterm` - Terminal emulator + +## Output + +### Logs Directory +All logs are saved to: `/tmp/graphics-test-logs/` + +Key files: +- `00-master-test.log` - Complete test output +- `prereq_system-check.log` - System information and prerequisites +- `app_gtk4-render-test-default.log` - GTK4 default renderer test +- `gtk4_render_test_default.png` - Visual verification screenshot +- `gtk4_render_test_ngl.png` - NGL renderer screenshot + +### Test Results Summary +The script outputs: +- Individual test results (PASS/FAIL/WARN/SKIP) +- OpenGL renderer information +- List of generated logs and screenshots +- Overall result: PASS or FAIL + +### RHTS Artifacts +The following artifacts are submitted to Testing Farm: +- Master log file +- All PNG screenshots (visual evidence) +- Failed test logs (for debugging) + +## Customization + +### Environment Variables +```bash +# Set custom log directory +LOGDIR=/var/log/graphics-test ./graphics-smoke-test-rhts.sh + +# Disable verbose logging +VERBOSE=0 ./graphics-smoke-test-rhts.sh + +# Force specific GTK renderer +GSK_RENDERER=ngl ./graphics-smoke-test-rhts.sh --phase=4 +``` + +### Manual GTK4 Render Test +```bash +# Test with default renderer +xwfb-run -w 2 -- python3 gtk4_render_test.py output.png + +# Test with NGL renderer +xwfb-run -w 2 -- python3 gtk4_render_test.py output_ngl.png --renderer=ngl + +# Test with other renderers +xwfb-run -w 2 -- python3 gtk4_render_test.py output_cairo.png --renderer=cairo +xwfb-run -w 2 -- python3 gtk4_render_test.py output_vulkan.png --renderer=vulkan +``` + +## Exit Codes + +- `0` - All critical tests passed +- `1` - One or more critical tests failed + +## Integration Examples + +### CI/CD Pipeline (.gitlab-ci.yml) +```yaml +graphics-test: + stage: test + script: + - cd universal_sanity_test + - ./graphics-smoke-test-rhts.sh + artifacts: + when: always + paths: + - /tmp/graphics-test-logs/ + expire_in: 7 days + tags: + - multiarch +``` + +### Testing Farm +```yaml +plans: + - name: graphics-smoke-test + discover: + how: fmf + execute: + how: tmt +``` + +## Troubleshooting + +### Tests Fail to Start +**Problem**: Missing dependencies + +**Solution**: The script auto-installs packages, but you can pre-install: +```bash +dnf install -y mesa-dri-drivers gtk4 mutter xwayland-run +``` + +### Black Screen Detected (s390x) +**Problem**: GTK4 apps show black windows + +**Solution**: This is a known Mesa bug on s390x. Use NGL renderer: +```bash +GSK_RENDERER=ngl gtk4-app +``` + +### glxgears Timeout +**Problem**: `glxgears` test times out or shows no FPS + +**Solution**: Check renderer: +```bash +xwfb-run -w 2 -- glxinfo | grep -i renderer +``` +Ensure software renderer (llvmpipe/swrast) is available. + +### xwfb-run Not Found +**Problem**: `xwayland-run` package not installed + +**Solution**: +```bash +dnf install -y xwayland-run +``` + +## Development & Contributions + +### Test Structure +Each test uses the `run_test()` wrapper: +```bash +run_test "test-name" \ + "command to execute" \ + "yes|no (critical)" +``` + +### Adding New Tests +1. Add test to appropriate phase function in `graphics-smoke-test-rhts.sh` +2. Use `run_test()` for automatic RHTS reporting +3. Update this README with test description + +### Color Coding +- Blue: Section headers +- Green: Pass +- Yellow: Warning (non-critical failure) +- Red: Fail (critical failure) + +## License + +Testing infrastructure for Mesa and GNOME graphics stack. + +## Contact + +- **Maintainer**: Peter Kopec pekopec@redhat.com +- **Components Tested**: mesa, gtk3, gtk4, mutter, xwayland +- **Tags**: CI-Tier-1, graphics, multiarch, infrastructure, rendering + +## See Also + +- [TMT Documentation](https://tmt.readthedocs.io/) +- [Mesa Project](https://www.mesa3d.org/) +- [GTK Documentation](https://www.gtk.org/) +- [GNOME Mutter](https://gitlab.gnome.org/GNOME/mutter) diff --git a/tests/graphics-smoke-test-tmt.sh b/tests/graphics-smoke-test-tmt.sh new file mode 100755 index 0000000..f97f392 --- /dev/null +++ b/tests/graphics-smoke-test-tmt.sh @@ -0,0 +1,794 @@ +#!/bin/bash +# +# Cross-Architecture Graphics Smoke Test with TMT Reporting +# Works on: x86_64, aarch64, ppc64le, s390x +# OS: RHEL 9+, Fedora 35+ +# +# Designed for Testing Farm / TMT environment +# Reports each test result using tmt-report-result +# + +#set -e +set -x # Enable verbose mode - show all commands + +# ============================================================================= +# Configuration +# ============================================================================= +TEST_TIMEOUT=30 +LOGDIR=${LOGDIR:-/tmp/graphics-test-logs} +OVERALL_RESULT="PASS" +VERBOSE=${VERBOSE:-1} # Verbose logging enabled by default + +# Create log directory +mkdir -p "$LOGDIR" + +# Log everything to a master log file +exec > >(tee -a "$LOGDIR/00-master-test.log") 2>&1 + +# Color output (disabled if not a terminal) +if [ -t 1 ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + NC='\033[0m' +else + RED='' + GREEN='' + YELLOW='' + BLUE='' + NC='' +fi + +# ============================================================================= +# Helper Functions +# ============================================================================= + +# TMT reporting helper +tmt_report() { + local test_name="$1" + local result="$2" # PASS/FAIL/WARN/SKIP + local logfile="$3" + + echo -e "${BLUE}[TMT]${NC} Reporting: $test_name = $result" + + if command -v tmt-report-result >/dev/null 2>&1; then + tmt-report-result -o "$logfile" "$test_name" "$result" + else + # Fallback when not in TMT environment + echo "TMT_REPORT: $test_name $result $logfile" + fi + + # Track overall failure (SKIP doesn't affect overall result) + if [ "$result" == "FAIL" ]; then + OVERALL_RESULT="FAIL" + fi +} + +# TMT log submission helper (for screenshots, images, etc.) +tmt_submit() { + local file="$1" + local description="${2:-$(basename $file)}" + + if [ ! -f "$file" ]; then + echo -e "${YELLOW}[TMT]${NC} Skip submit: $file (not found)" + return 1 + fi + + echo -e "${BLUE}[TMT]${NC} Submitting: $description" + + if command -v tmt-file-submit >/dev/null 2>&1; then + tmt-file-submit -l "$file" + else + # Fallback when not in TMT environment + echo "TMT_SUBMIT: $file ($description)" + fi +} + +# Test execution wrapper +run_test() { + local test_name="$1" + local test_cmd="$2" + local critical="${3:-yes}" # yes/no + local logfile="$LOGDIR/${test_name//\//_}" # Replace / with _ + logfile="${logfile//[: ]/_}.log" # Replace : and space with _ + + echo "========================================" + echo "Running: $test_name" + echo "Command: $test_cmd" + echo "Log: $logfile" + echo -n "Result: " + + if eval "$test_cmd" >"$logfile" 2>&1; then + echo -e "${GREEN}PASS${NC}" + + # Show last few lines of output if verbose + if [ "$VERBOSE" == "1" ] && [ -s "$logfile" ]; then + echo " Last output lines:" + tail -5 "$logfile" | sed 's/^/ /' + fi + tmt_report "$test_name" "PASS" "$logfile" + return 0 + else + local exit_code=$? + echo $exit_code >> "$logfile" + if [ "$critical" == "yes" ]; then + echo -e "${RED}FAIL${NC} (exit code: $exit_code)" + + # Always show failure details + echo " Error details:" + tail -20 "$logfile" | sed 's/^/ /' + + tmt_report "$test_name" "FAIL" "$logfile" + return 1 + else + echo -e "${YELLOW}WARN${NC} (optional, exit code: $exit_code)" + + # Show warning details if verbose + if [ "$VERBOSE" == "1" ] && [ -s "$logfile" ]; then + echo " Warning details:" + tail -10 "$logfile" | sed 's/^/ /' + fi + + tmt_report "$test_name" "WARN" "$logfile" + return 0 + fi + fi +} + +# ============================================================================= +# Dependency Installation +# ============================================================================= + +install_dependencies() { + local logfile="$LOGDIR/00-install-dependencies.log" + + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE} Installing Dependencies${NC}" + echo -e "${BLUE}========================================${NC}" + + # Required packages + local required_packages=( + mesa-dri-drivers + mesa-libGL + mesa-libEGL + gtk3 + gtk4 + xwayland-run + mutter + mesa-demos + python3-gobject + ) + + # Optional packages + local optional_packages=( + gtk3-devel + gtk4-devel + gtk4-devel-tools + xterm + ) + + echo "Installing required packages..." | tee "$logfile" + + if command -v dnf &>/dev/null; then + # Install required packages + if dnf install -y "${required_packages[@]}" >>"$logfile" 2>&1; then + echo -e "${GREEN}✓${NC} Required packages installed" + tmt_report "install-dependencies" "PASS" "$logfile" + else + echo -e "${RED}✗${NC} Failed to install required packages" + tmt_report "install-dependencies" "FAIL" "$logfile" + echo "Check log: $logfile" + exit 1 + fi + + # Install optional packages (non-fatal) + echo "Installing optional packages..." | tee -a "$logfile" + for pkg in "${optional_packages[@]}"; do + dnf install -y "$pkg" >>"$logfile" 2>&1 || echo " Skipped: $pkg" + done + tmt_submit "$logfile" "Optional extra dependencies" + else + echo -e "${RED}✗${NC} dnf package manager not found" + tmt_report "install-dependencies" "FAIL" "$logfile" + exit 1 + fi + + echo +} + +# ============================================================================= +# Phase Functions +# ============================================================================= + +run_phases_1_to_3() { + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE} Phases 1-3: Infrastructure Tests${NC}" + echo -e "${BLUE}========================================${NC}" + echo "Architecture: $(uname -m)" + echo "Kernel: $(uname -r)" + echo "OS: $(cat /etc/redhat-release 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)" + echo "Log directory: $LOGDIR" + echo "Date: $(date)" + echo -e "${BLUE}========================================${NC}" + echo + + # Environment debugging + echo -e "${BLUE}Environment Check:${NC}" + echo "Display: ${DISPLAY:-not set}" + echo "Wayland: ${WAYLAND_DISPLAY:-not set}" + echo "XDG Runtime: ${XDG_RUNTIME_DIR:-not set}" + echo "Session type: ${XDG_SESSION_TYPE:-not set}" + echo + + # Check what's already available + echo -e "${BLUE}Pre-flight Check:${NC}" + echo " mutter: $(which mutter 2>/dev/null || echo 'NOT FOUND')" + echo " xwfb-run: $(which xwfb-run 2>/dev/null || echo 'NOT FOUND')" + echo " glxinfo: $(which glxinfo 2>/dev/null || echo 'NOT FOUND')" + echo " gtk3-demo: $(which gtk3-demo 2>/dev/null || echo 'NOT FOUND')" + echo + + # Check DRI drivers + echo " DRI drivers:" + if [ -d /usr/lib64/dri ]; then + ls /usr/lib64/dri/*_dri.so 2>/dev/null | head -5 | sed 's/^/ /' || echo " NONE FOUND" + else + echo " /usr/lib64/dri NOT FOUND" + fi + echo + + # Install dependencies first + install_dependencies + + # ========================================================================= + # PHASE 1: System Info & Prerequisites Check + # ========================================================================= + echo -e "${BLUE}Phase 1: System Information & Prerequisites${NC}" + echo "========================================" + echo "Running: prereq/system-check" + + local prereq_log="$LOGDIR/prereq_system-check.log" + echo "Log: $prereq_log" + echo -n "Result: " + + # All checks go into one log file (use subshell so exit doesn't kill parent) + ( + echo "=========================================" + echo "SYSTEM INFORMATION & PREREQUISITES CHECK" + echo "=========================================" + echo "Date: $(date)" + echo + + # Hardware & System Info + echo "--- Architecture & Kernel ---" + uname -a + echo "Architecture: $(uname -m)" + echo + + echo "--- CPU Information ---" + if [ -f /proc/cpuinfo ]; then + head -30 /proc/cpuinfo + fi + echo + + echo "--- Memory Information ---" + free -h + echo + + echo "--- PCI Devices ---" + if command -v lspci >/dev/null 2>&1; then + lspci -v + echo + echo "VGA Devices:" + lspci | grep -i vga || echo "No VGA devices found" + else + echo "lspci not available (expected on s390x)" + fi + echo + + echo "--- Hardware Details ---" + if command -v lshw >/dev/null 2>&1; then + lshw -short 2>&1 || echo "lshw failed" + else + echo "lshw not available" + fi + echo + + echo "=========================================" + echo "PREREQUISITE CHECKS" + echo "=========================================" + echo + + # Check critical packages + prereq_failed=0 + + echo "--- Mesa DRI Drivers ---" + if rpm -q mesa-dri-drivers; then + echo "✓ PASS" + else + echo "✗ FAIL - mesa-dri-drivers not installed" + prereq_failed=1 + fi + echo + + echo "--- GTK3 ---" + if rpm -q gtk3; then + echo "✓ PASS" + else + echo "✗ WARN - gtk3 not installed (optional)" + fi + echo + + echo "--- GTK4 ---" + if rpm -q gtk4; then + echo "✓ PASS" + else + echo "✗ FAIL - gtk4 not installed" + prereq_failed=1 + fi + echo + + echo "--- xwfb-run (headless testing) ---" + if which xwfb-run; then + echo "✓ PASS" + else + echo "✗ FAIL - xwfb-run not found" + prereq_failed=1 + fi + echo + + echo "--- Wayland Compositor ---" + if rpm -q mutter >/dev/null 2>&1; then + rpm -q mutter + echo "✓ PASS - mutter (GNOME) found" + elif rpm -q kwin >/dev/null 2>&1; then + rpm -q kwin + echo "✓ PASS - kwin (KDE) found" + elif rpm -q weston >/dev/null 2>&1; then + rpm -q weston + echo "✓ PASS - weston found" + else + echo "✗ FAIL - No compositor found (mutter/kwin/weston)" + prereq_failed=1 + fi + echo + + echo "--- Mesa/GL/EGL Packages ---" + rpm -qa | grep -E 'mesa|libGL|libEGL' | sort || echo "No Mesa packages found" + echo + + echo "--- DRI Drivers ---" + if [ -d /usr/lib64/dri ]; then + ls -lh /usr/lib64/dri/ | grep '_dri.so$' || echo "No DRI drivers found" + else + echo "/usr/lib64/dri directory not found" + fi + echo + + echo "=========================================" + if [ $prereq_failed -eq 0 ]; then + echo "RESULT: All prerequisites satisfied" + exit 0 + else + echo "RESULT: Missing critical prerequisites" + exit 1 + fi + + ) > "$prereq_log" 2>&1 + + if [ $? -eq 0 ]; then + echo -e "${GREEN}PASS${NC}" + tmt_report "prereq/system-check" "PASS" "$prereq_log" + else + echo -e "${RED}FAIL${NC}" + echo " Missing prerequisites - check log for details" + tail -20 "$prereq_log" | sed 's/^/ /' + tmt_report "prereq/system-check" "FAIL" "$prereq_log" + OVERALL_RESULT="FAIL" + fi + + # Submit the comprehensive log + tmt_submit "$prereq_log" "system-check-full.log" + echo + + # ========================================================================= + # PHASE 2: Library Initialization Tests + # ========================================================================= + echo -e "${BLUE}Phase 2: Graphics Library Initialization${NC}" + + run_test "libinit/mesa-gl" \ + "timeout 5 bash -c 'LD_PRELOAD=/usr/lib64/libGL.so.1 /bin/true 2>&1'" + + run_test "libinit/gtk3" \ + "timeout 5 python3 -c 'import gi; gi.require_version(\"Gtk\", \"3.0\"); from gi.repository import Gtk' 2>&1" + + run_test "libinit/gtk4" \ + "timeout 5 python3 -c 'import gi; gi.require_version(\"Gtk\", \"4.0\"); from gi.repository import Gtk' 2>&1" \ + "no" + + # Check software rendering + ls -l /usr/lib64/ > /tmp/mesa_drivers.txt + if [ -f /usr/lib64/dri/swrast_dri.so ] || [ -f /usr/lib64/dri/kms_swrast_dri.so ]; then + echo -e " Software renderer: ${GREEN}available${NC} (llvmpipe/swrast)" + tmt_report "libinit/swrast-available" "PASS" "/tmp/mesa_drivers.txt" + else + echo -e " Software renderer: ${YELLOW}not found${NC}" + tmt_report "libinit/swrast-available" "WARN" "/tmp/mesa_drivers.txt" + fi + + echo + + # ========================================================================= + # PHASE 3: Headless Graphics Stack Tests + # ========================================================================= + echo -e "${BLUE}Phase 3: Headless Graphics Stack Tests${NC}" + + run_test "headless/compositor-start" \ + "timeout $TEST_TIMEOUT xwfb-run -w 2 -- true" + + run_test "headless/ptyxis-help" \ + "timeout $TEST_TIMEOUT xwfb-run -w 2 -- ptyxis --help" + + # Get and report renderer info + local renderer_log="$LOGDIR/headless-renderer-info.log" + if timeout $TEST_TIMEOUT xwfb-run -w 2 -- glxinfo >"$renderer_log" 2>&1; then + RENDERER=$(grep "OpenGL renderer" "$renderer_log" | cut -d: -f2 | xargs) + VENDOR=$(grep "OpenGL vendor" "$renderer_log" | cut -d: -f2 | xargs) + VERSION=$(grep "OpenGL version" "$renderer_log" | cut -d: -f2 | xargs) + + echo -e " OpenGL vendor: ${GREEN}${VENDOR}${NC}" + echo -e " OpenGL renderer: ${GREEN}${RENDERER}${NC}" + echo -e " OpenGL version: ${GREEN}${VERSION}${NC}" + + tmt_report "headless/renderer-info" "PASS" "$renderer_log" + else + tmt_report "headless/renderer-info" "FAIL" "$renderer_log" + fi + + echo +} + +run_phase_4() { + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE} Phase 4: Application Rendering Tests${NC}" + echo -e "${BLUE}========================================${NC}" + echo "Architecture: $(uname -m)" + echo "Log directory: $LOGDIR" + echo -e "${BLUE}========================================${NC}" + echo + + # ========================================================================= + # PHASE 4: Application Startup Tests + # ========================================================================= + echo -e "${BLUE}Phase 4: Application Startup Tests${NC}" + + # Check GTK renderer being used + echo "GTK Renderer Detection:" + echo " GTK_RENDERER env: ${GTK_RENDERER:-not set (will use default)}" + echo " GSK_RENDERER env: ${GSK_RENDERER:-not set}" + echo " GTK_DEBUG env: ${GTK_DEBUG:-not set}" + echo " Expected: GTK4 uses 'ngl' (new GL) or 'gl' renderer" + + # Test GTK3 app (may not be installed on RHEL 10) + if which gtk3-demo >/dev/null 2>&1; then + run_test "app/gtk3-demo-list" \ + "timeout $TEST_TIMEOUT xwfb-run -w 2 -- gtk3-demo --list" + #test some actual demo + run_test "app/gtk3-demo-textscroll" \ + "timeout $TEST_TIMEOUT xwfb-run -w 2 -- gtk3-demo --run=textscroll --autoquit" + else + echo "========================================" + echo "Running: app/gtk3-demo" + echo "Command: gtk3-demo --list" + echo -n "Result: " + echo -e "${YELLOW}SKIP${NC} (gtk3-demo not installed)" + tmt_report "app/gtk3-demo" "SKIP" "/dev/null" + fi + + + # Test GTK4 + run_test "app/gtk4-demo-list" \ + "timeout $TEST_TIMEOUT xwfb-run -w 2 -- gtk4-demo --list" + + #Test some actual demo + run_test "app/gtk4-demo-textscroll" \ + "timeout $TEST_TIMEOUT xwfb-run -w 2 -- gtk4-demo --run=textscroll --autoquit" + + # Test GTK4 rendering with actual visual verification + local gtk4_test_script="$(dirname "$0")/gtk4_render_test.py" + if [ ! -f "$gtk4_test_script" ]; then + gtk4_test_script="./gtk4_render_test.py" + fi + + if [ -f "$gtk4_test_script" ] && python3 -c "import gi; gi.require_version('Gtk', '4.0')" 2>/dev/null; then + # ===================================================================== + # Test 1: GTK4 with default renderer + # ===================================================================== + echo "========================================" + echo "Running: app/gtk4-render-test-default (visual verification)" + local gtk4_render_log="$LOGDIR/app_gtk4-render-test-default.log" + local gtk4_render_png="$LOGDIR/gtk4_render_test_default.png" + echo "Log: $gtk4_render_log" + echo "Output: $gtk4_render_png" + echo -n "Result: " + + # Test with default renderer + if timeout $TEST_TIMEOUT xwfb-run -w 2 -- python3 "$gtk4_test_script" "$gtk4_render_png" >"$gtk4_render_log" 2>&1; then + # Check if PNG has good color diversity + if [ -f "$gtk4_render_png" ]; then + local colors=$(python3 -c " +import sys +with open('$gtk4_render_png', 'rb') as f: + data = f.read() + samples = set() + for i in range(100, min(len(data)-4, 2000), 10): + samples.add(data[i:i+4]) + print(len(samples)) +" 2>/dev/null || echo 0) + + if [ "$colors" -gt 50 ]; then + echo -e "${GREEN}PASS${NC} (${colors} unique colors - rendering OK)" + tmt_report "app/gtk4-render-test-default" "PASS" "$gtk4_render_log" + tmt_submit "$gtk4_render_png" "gtk4-render-test-default.png" + else + echo -e "${RED}FAIL${NC} (only ${colors} unique colors - black screen detected)" + tmt_report "app/gtk4-render-test-default" "FAIL" "$gtk4_render_log" + tmt_submit "$gtk4_render_png" "gtk4-render-FAILED-default.png" + fi + else + echo -e "${RED}FAIL${NC} (no PNG generated)" + tmt_report "app/gtk4-render-test-default" "FAIL" "$gtk4_render_log" + fi + else + echo -e "${RED}FAIL${NC} (app crashed or timed out)" + tmt_report "app/gtk4-render-test-default" "FAIL" "$gtk4_render_log" + fi + + # ===================================================================== + # Test 2: GTK4 with NGL renderer (optional/informational test) + # ===================================================================== + echo "========================================" + echo "Running: app/gtk4-render-test-ngl (visual verification with GSK_RENDERER=ngl)" + echo "Note: NGL renderer test is OPTIONAL - verifies if workaround is available" + local gtk4_ngl_log="$LOGDIR/app_gtk4-render-test-ngl.log" + local gtk4_ngl_png="$LOGDIR/gtk4_render_test_ngl.png" + echo "Log: $gtk4_ngl_log" + echo "Output: $gtk4_ngl_png" + echo -n "Result: " + + # Test with NGL renderer (non-critical - NGL may not be fully supported yet) + if timeout $TEST_TIMEOUT xwfb-run -w 2 -- python3 "$gtk4_test_script" "$gtk4_ngl_png" --renderer=ngl >"$gtk4_ngl_log" 2>&1; then + # Check if PNG has good color diversity + if [ -f "$gtk4_ngl_png" ]; then + local ngl_colors=$(python3 -c " +import sys +with open('$gtk4_ngl_png', 'rb') as f: + data = f.read() + samples = set() + for i in range(100, min(len(data)-4, 2000), 10): + samples.add(data[i:i+4]) + print(len(samples)) +" 2>/dev/null || echo 0) + + if [ "$ngl_colors" -gt 50 ]; then + echo -e "${GREEN}PASS${NC} (${ngl_colors} unique colors - NGL renderer works!)" + tmt_report "app/gtk4-render-test-ngl" "PASS" "$gtk4_ngl_log" + tmt_submit "$gtk4_ngl_png" "gtk4-render-test-ngl.png" + else + echo -e "${YELLOW}WARN${NC} (only ${ngl_colors} unique colors - NGL renderer also has issues)" + tmt_report "app/gtk4-render-test-ngl" "WARN" "$gtk4_ngl_log" + tmt_submit "$gtk4_ngl_png" "gtk4-render-ngl-warn.png" + fi + else + echo -e "${YELLOW}WARN${NC} (no PNG generated - NGL renderer failed)" + tmt_report "app/gtk4-render-test-ngl" "WARN" "$gtk4_ngl_log" + fi + else + echo -e "${YELLOW}WARN${NC} (app crashed or timed out - NGL renderer not supported/broken)" + tmt_report "app/gtk4-render-test-ngl" "WARN" "$gtk4_ngl_log" + fi + else + echo "========================================" + echo "Running: app/gtk4-render-test" + echo -n "Result: " + echo -e "${YELLOW}SKIP${NC} (gtk4_render_test.py or GTK4 not available)" + tmt_report "app/gtk4-render-test-default" "SKIP" "/dev/null" + tmt_report "app/gtk4-render-test-ngl" "SKIP" "/dev/null" + fi + + if which xterm >/dev/null 2>&1; then + run_test "app/xterm" \ + "timeout $TEST_TIMEOUT xwfb-run -w 2 -- xterm -e true" \ + "no" + fi + + if which glxgears >/dev/null 2>&1; then + # glxgears runs forever, so we test differently: + # Run for 5 seconds, check if it produces FPS output + echo "========================================" + echo "Running: app/glxgears" + echo "Command: xwfb-run -w 2 -- glxgears -info & sleep 25 ; killall glxgears" + local gears_log="$LOGDIR/app_glxgears.log" + echo "Log: $gears_log" + echo -n "Result: " + + # Run glxgears for 5 seconds with kill timeout + xwfb-run -w 2 -- glxgears -info >"$gears_log" 2>&1 & + sleep 25 + killall glxgears + local exit_code=$? + + # Wait a moment for cleanup + sleep 1 + + # Check if we got FPS output (means it rendered successfully) + if grep -q "frames in.*seconds.*FPS" "$gears_log"; then + echo -e "${GREEN}PASS${NC} (rendered frames successfully)" + if [ "$VERBOSE" == "1" ]; then + echo " GL Renderer:" + grep "GL_RENDERER" "$gears_log" | sed 's/^/ /' + echo " Performance:" + grep "frames in.*seconds.*FPS" "$gears_log" | head -3 | sed 's/^/ /' + fi + tmt_report "app/glxgears" "PASS" "$gears_log" + else + echo -e "${YELLOW}WARN${NC} (no FPS output, exit code: $exit_code)" + if [ "$VERBOSE" == "1" ]; then + echo " Log output:" + tail -10 "$gears_log" | sed 's/^/ /' + fi + tmt_report "app/glxgears" "WARN" "$gears_log" + fi + + # Cleanup any remaining processes + killall -9 glxgears mutter dbus-run-session 2>/dev/null || true + fi + + echo +} + +# ============================================================================= +# Main Test Execution +# ============================================================================= + +main() { + # Parse command-line arguments + local RUN_PHASES="all" # Default: run all phases + + while [[ $# -gt 0 ]]; do + case $1 in + --phase=*) + RUN_PHASES="${1#*=}" + shift + ;; + --help) + echo "Usage: $0 [--phase=1-3|4|all]" + echo " --phase=1-3 Run only infrastructure tests (Phases 1-3)" + echo " --phase=4 Run only application rendering tests (Phase 4)" + echo " --phase=all Run all phases (default)" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac + done + + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE} Graphics Stack Smoke Test${NC}" + echo -e "${BLUE}========================================${NC}" + echo "Run mode: $RUN_PHASES" + echo "Architecture: $(uname -m)" + echo + + # Run requested phases + case "$RUN_PHASES" in + "1-3") + run_phases_1_to_3 + ;; + "4") + run_phase_4 + ;; + "all") + run_phases_1_to_3 + run_phase_4 + ;; + *) + echo "ERROR: Invalid phase specification: $RUN_PHASES" + echo "Valid options: 1-3, 4, all" + exit 1 + ;; + esac + + # ========================================================================= + # Results Summary + # ========================================================================= + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE} Test Results Summary${NC}" + echo -e "${BLUE}========================================${NC}" + + # Show all logs + echo "Generated logs:" + ls -lh "$LOGDIR" | tail -n +2 | awk '{printf " %s %s\n", $9, $5}' + echo + + if [ "$OVERALL_RESULT" == "PASS" ]; then + echo -e "${GREEN}✓ ALL CRITICAL TESTS PASSED${NC}" + echo -e "${BLUE}========================================${NC}" + echo "Graphics stack is functional on $(uname -m)" + echo "Logs saved to: $LOGDIR" + echo "Master log: $LOGDIR/00-master-test.log" + + # Submit master log and any screenshots + echo + echo "Submitting artifacts to TMT..." + tmt_submit "$LOGDIR/00-master-test.log" "master-test.log" + + # Submit any PNG screenshots as visual evidence + for png in "$LOGDIR"/*.png; do + if [ -f "$png" ]; then + tmt_submit "$png" "$(basename $png)" + fi + done + + # Final overall report + tmt_report "graphics-smoke-test" "PASS" "$LOGDIR/00-master-test.log" + exit 0 + else + echo -e "${RED}✗ ONE OR MORE CRITICAL TESTS FAILED${NC}" + echo -e "${BLUE}========================================${NC}" + echo "Graphics stack validation failed" + echo "Logs saved to: $LOGDIR" + echo "Master log: $LOGDIR/00-master-test.log" + echo + + # Show detailed failure information + echo -e "${RED}Failed Tests:${NC}" + if ls "$LOGDIR"/*.log >/dev/null 2>&1; then + for logfile in "$LOGDIR"/*.log; do + testname=$(basename "$logfile" .log) + # Check if it's a failed test (non-zero exit or error keywords) + if grep -qi "error\|fail\|cannot\|unable" "$logfile" 2>/dev/null; then + echo -e " ${RED}✗${NC} $testname" + echo " Log: $logfile" + echo " Last 10 lines:" + tail -10 "$logfile" | sed 's/^/ /' + echo + fi + done + fi + + # Submit master log and all artifacts for debugging + echo + echo "Submitting artifacts to TMT..." + tmt_submit "$LOGDIR/00-master-test.log" "master-test.log" + + # Submit all PNG screenshots as evidence of failure + for png in "$LOGDIR"/*.png; do + if [ -f "$png" ]; then + tmt_submit "$png" "$(basename $png)" + fi + done + + # Submit failed test logs + for log in "$LOGDIR"/*.log; do + if [ -f "$log" ] && grep -qi "error\|fail" "$log" 2>/dev/null; then + tmt_submit "$log" "$(basename $log)" + fi + done + + # Final overall report + tmt_report "graphics-smoke-test" "FAIL" "$LOGDIR/00-master-test.log" + exit 1 + fi +} + +# ============================================================================= +# Script Entry Point +# ============================================================================= + +# Pass all command-line arguments to main +main "$@" diff --git a/tests/gtk4_render_test.py b/tests/gtk4_render_test.py new file mode 100755 index 0000000..b51ee8e --- /dev/null +++ b/tests/gtk4_render_test.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +""" +GTK4 Rendering Test - Verifies actual rendering by drawing and saving +Tests the s390x Mesa bug where GTK apps show black screens +""" + +import sys +import os + +# Parse renderer argument BEFORE importing GTK +# GSK_RENDERER must be set before GTK initializes +renderer = None +output_file = None + +for i, arg in enumerate(sys.argv[1:], 1): + if arg.startswith('--renderer='): + renderer = arg.split('=', 1)[1] + elif not arg.startswith('--'): + output_file = arg + +if renderer: + os.environ['GSK_RENDERER'] = renderer + print(f"Setting GSK_RENDERER={renderer} before GTK import") + +#Set debug to print rear renderer of app +os.environ['GSK_DEBUG'] = "renderer" + +# Now import GTK (after setting environment) +import gi +gi.require_version('Gtk', '4.0') +from gi.repository import Gtk, GLib +import cairo + +class RenderTestApp(Gtk.Application): + def __init__(self, output_file): + super().__init__(application_id='org.test.gtk4render') + self.output_file = output_file + self.rendered = False + self.drawing_area = None + + def do_activate(self): + window = Gtk.ApplicationWindow(application=self) + window.set_title("GTK4 Rendering Test") + window.set_default_size(400, 300) + + self.drawing_area = Gtk.DrawingArea() + self.drawing_area.set_draw_func(self.draw_callback) + + window.set_child(self.drawing_area) + window.present() + + # Wait for rendering, then save and quit + GLib.timeout_add(2000, self.save_and_quit) + + def draw_callback(self, area, cr, width, height): + """Draw colorful pattern to prove rendering works""" + # Red rectangle + cr.set_source_rgb(1.0, 0.0, 0.0) + cr.rectangle(10, 10, 100, 100) + cr.fill() + + # Green rectangle + cr.set_source_rgb(0.0, 1.0, 0.0) + cr.rectangle(130, 10, 100, 100) + cr.fill() + + # Blue rectangle + cr.set_source_rgb(0.0, 0.0, 1.0) + cr.rectangle(250, 10, 100, 100) + cr.fill() + + # Yellow circle + cr.set_source_rgb(1.0, 1.0, 0.0) + cr.arc(200, 200, 50, 0, 2 * 3.14159) + cr.fill() + + print(f"Draw callback called - rendering {width}x{height} surface") + self.rendered = True + + def save_and_quit(self): + """Save the rendered surface to PNG and exit""" + if not self.rendered: + print("WARNING: Draw function not called yet") + print("RESULT: WARN - Rendering may not have occurred") + self.quit() + return False + + try: + # Get the window and widget dimensions + width = self.drawing_area.get_width() + height = self.drawing_area.get_height() + + print(f"Creating Cairo surface {width}x{height}") + + # Create a Cairo image surface + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + cr = cairo.Context(surface) + + # Re-render to this surface + self.draw_callback(None, cr, width, height) + + # Save to PNG + surface.write_to_png(self.output_file) + + # Check file was created and has reasonable size + if os.path.exists(self.output_file): + file_size = os.path.getsize(self.output_file) + print(f"SUCCESS: Saved rendering to {self.output_file} ({file_size} bytes)") + + if file_size > 1000: + print("RESULT: PASS - Rendering verified") + else: + print(f"RESULT: FAIL - PNG file too small ({file_size} bytes)") + else: + print(f"RESULT: FAIL - Failed to create {self.output_file}") + + except Exception as e: + print(f"ERROR: {e}") + import traceback + traceback.print_exc() + print("RESULT: FAIL - Exception during rendering") + + self.quit() + return False + + +def main(): + global output_file + + if not output_file: + print(f"Usage: {sys.argv[0]} [--renderer=ngl|gl|cairo|vulkan]") + print(f" --renderer: GSK renderer to use (default: auto-detect)") + sys.exit(1) + + print(f"GTK4 Rendering Test - will save to {output_file}") + print(f"GSK_RENDERER: {os.environ.get('GSK_RENDERER', 'not set (auto-detect)')}") + print(f"GTK_RENDERER: {os.environ.get('GTK_RENDERER', 'not set')}") + + app = RenderTestApp(output_file) + exit_status = app.run(None) + + sys.exit(exit_status) + + +if __name__ == '__main__': + main()