强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Flatpak 应用打包完整教程 / 第 10 章:测试与调试

第 10 章:测试与调试

本章目标:掌握 Flatpak 应用的测试方法、调试技巧、权限审计流程和 CI/CD 集成。


10.1 测试策略概述

10.1.1 测试层次

层次测试内容工具
构建测试Manifest 语法、构建流程flatpak-builderjsonlint
单元测试应用功能测试应用自身的测试框架
沙箱测试权限、隔离验证flatpak runbwrap
集成测试应用与桌面环境交互虚拟机、容器
兼容性测试多发行版验证多发行版 Docker 镜像
安全审计权限合理性评估flatpak info、Flatseal

10.1.2 测试流程

代码提交
    │
    ├─→ Manifest 验证 (JSON 语法、AppStream 验证)
    │
    ├─→ 离线构建测试 (flatpak-builder --disable-download)
    │
    ├─→ 沙箱功能测试 (flatpak run + 自动化测试)
    │
    ├─→ 权限审计 (最小权限检查)
    │
    ├─→ 兼容性测试 (多发行版 x 多桌面环境)
    │
    └─→ 发布审核

10.2 Manifest 验证

10.2.1 JSON 语法验证

# 使用 Python 验证 JSON 语法
python3 -m json.tool com.example.App.json > /dev/null && echo "JSON 语法正确" || echo "JSON 语法错误"

# 使用 jq 验证
jq empty com.example.App.json && echo "JSON 语法正确" || echo "JSON 语法错误"

# 检查必需字段
jq -e '.["app-id"]' com.example.App.json > /dev/null || echo "缺少 app-id"
jq -e '.["runtime"]' com.example.App.json > /dev/null || echo "缺少 runtime"
jq -e '.["sdk"]' com.example.App.json > /dev/null || echo "缺少 sdk"
jq -e '.["command"]' com.example.App.json > /dev/null || echo "缺少 command"
jq -e '.["modules"]' com.example.App.json > /dev/null || echo "缺少 modules"

10.2.2 AppStream 元数据验证

# 安装验证工具
sudo apt install appstream  # Ubuntu/Debian
sudo dnf install appstream  # Fedora

# 验证 AppStream 元数据
appstreamcli validate --pedantic com.example.App.metainfo.xml

# 输出示例:
# com.example.App.metainfo.xml: OK
# 或列出问题:
# E: com.example.App.metainfo.xml:5: cid-is-not-rdns
# W: com.example.App.metainfo.xml:10: description-has-no-summary

10.2.3 Desktop 文件验证

# 安装验证工具
sudo apt install desktop-file-utils  # Ubuntu/Debian
sudo dnf install desktop-file-utils  # Fedora

# 验证桌面文件
desktop-file-validate com.example.App.desktop

# 常见问题:
# - 缺少 Categories 字段
# - Icon 路径不正确
# - Exec 命令格式错误

10.2.4 自动化验证脚本

#!/bin/bash
# validate-flatpak.sh - Flatpak 应用验证脚本

set -e

MANIFEST="$1"
METADATA="$2"
DESKTOP="$3"

ERRORS=0

echo "=== Flatpak 应用验证 ==="
echo ""

# 1. JSON 语法验证
echo "1. 验证 Manifest JSON 语法..."
if jq empty "$MANIFEST" 2>/dev/null; then
    echo "   ✓ JSON 语法正确"
else
    echo "   ✗ JSON 语法错误"
    jq . "$MANIFEST" 2>&1 | head -5
    ERRORS=$((ERRORS + 1))
fi

# 2. 必需字段检查
echo "2. 检查必需字段..."
for field in "app-id" "runtime" "sdk" "command" "modules"; do
    if jq -e ".[\"$field\"]" "$MANIFEST" > /dev/null 2>&1; then
        echo "   ✓ $field 存在"
    else
        echo "   ✗ $field 缺失"
        ERRORS=$((ERRORS + 1))
    fi
done

# 3. 应用 ID 格式检查
echo "3. 验证应用 ID 格式..."
APP_ID=$(jq -r '.["app-id"]' "$MANIFEST")
if [[ "$APP_ID" =~ ^[a-z][a-z0-9._-]+\.[a-z][a-z0-9._-]+$ ]]; then
    echo "   ✓ 应用 ID 格式正确: $APP_ID"
else
    echo "   ✗ 应用 ID 格式不正确: $APP_ID"
    ERRORS=$((ERRORS + 1))
fi

# 4. AppStream 验证
if [ -n "$METADATA" ] && [ -f "$METADATA" ]; then
    echo "4. 验证 AppStream 元数据..."
    if appstreamcli validate --pedantic "$METADATA" 2>/dev/null; then
        echo "   ✓ AppStream 元数据正确"
    else
        echo "   ✗ AppStream 元数据有问题"
        appstreamcli validate --pedantic "$METADATA" 2>&1 | head -10
        ERRORS=$((ERRORS + 1))
    fi
fi

# 5. Desktop 文件验证
if [ -n "$DESKTOP" ] && [ -f "$DESKTOP" ]; then
    echo "5. 验证 Desktop 文件..."
    if desktop-file-validate "$DESKTOP" 2>/dev/null; then
        echo "   ✓ Desktop 文件正确"
    else
        echo "   ✗ Desktop 文件有问题"
        desktop-file-validate "$DESKTOP" 2>&1
        ERRORS=$((ERRORS + 1))
    fi
fi

# 6. 离线构建检查
echo "6. 检查是否支持离线构建..."
if jq -e '.["build-options"]["build-args"][] | select(. == "--share-network")' "$MANIFEST" > /dev/null 2>&1; then
    echo "   ✗ Manifest 中启用了网络构建(Flathub 不允许)"
    ERRORS=$((ERRORS + 1))
else
    echo "   ✓ 无网络构建依赖"
fi

echo ""
if [ $ERRORS -eq 0 ]; then
    echo "=== 验证通过 ==="
    exit 0
else
    echo "=== 发现 $ERRORS 个问题 ==="
    exit 1
fi

10.3 沙箱测试

10.3.1 沙箱功能测试

# 测试 1:网络访问
echo "测试 1: 网络访问"
flatpak run --command=curl org.gnome.Calculator https://example.com -s -o /dev/null && \
    echo "  ✓ 网络访问正常" || echo "  ✗ 网络访问失败"

# 测试 2:文件系统访问
echo "测试 2: 文件系统访问"
flatpak run --command=bash org.gnome.Calculator -c "touch /tmp/test && rm /tmp/test" && \
    echo "  ✓ /tmp 可写" || echo "  ✗ /tmp 不可写"

# 测试 3:主目录访问
echo "测试 3: 主目录访问"
flatpak run --command=bash org.gnome.Calculator -c "ls ~/.config" 2>/dev/null && \
    echo "  ✓ ~/.config 可读" || echo "  ✗ ~/.config 不可读"

# 测试 4:GPU 访问
echo "测试 4: GPU 访问"
flatpak run --command=ls org.gnome.Calculator /dev/dri/ 2>/dev/null && \
    echo "  ✓ GPU 设备可访问" || echo "  ✗ GPU 设备不可访问"

10.3.2 权限测试自动化

#!/bin/bash
# test-sandbox.sh - 沙箱权限自动化测试

APP_ID="$1"
TESTS_PASSED=0
TESTS_FAILED=0

run_test() {
    local name="$1"
    local command="$2"
    local expect_success="$3"
    
    if flatpak run --command=bash "$APP_ID" -c "$command" &>/dev/null; then
        if [ "$expect_success" = "true" ]; then
            echo "  ✓ $name"
            TESTS_PASSED=$((TESTS_PASSED + 1))
        else
            echo "  ✗ $name (应失败但成功了)"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        fi
    else
        if [ "$expect_success" = "false" ]; then
            echo "  ✓ $name (正确拒绝)"
            TESTS_PASSED=$((TESTS_PASSED + 1))
        else
            echo "  ✗ $name (应成功但失败了)"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        fi
    fi
}

echo "=== 沙箱测试: $APP_ID ==="

echo ""
echo "--- 文件系统测试 ---"
run_test "读取 /tmp" "ls /tmp" "true"
run_test "写入 /tmp" "touch /tmp/flatpak-test && rm /tmp/flatpak-test" "true"
run_test "读取 /etc/passwd" "cat /etc/passwd" "true"
run_test "写入 /etc" "touch /etc/test" "false"
run_test "读取主目录" "ls ~" "true"

echo ""
echo "--- 设备测试 ---"
run_test "访问 GPU" "ls /dev/dri/" "true"
run_test "访问摄像头" "ls /dev/video0" "false"

echo ""
echo "--- 网络测试 ---"
run_test "DNS 解析" "nslookup example.com" "true"
run_test "HTTP 请求" "curl -s https://example.com" "true"

echo ""
echo "=== 结果: $TESTS_PASSED 通过, $TESTS_FAILED 失败 ==="
[ $TESTS_FAILED -eq 0 ] && exit 0 || exit 1

10.3.3 使用 strace 调试沙箱

# 跟踪系统调用(在沙箱外运行)
strace -f flatpak run org.gnome.Calculator 2>&1 | grep -i "permission\|denied\|error"

# 过滤特定系统调用
strace -e trace=file flatpak run org.gnome.Calculator 2>&1 | grep -v "ENOENT"

# 跟踪网络调用
strace -e trace=network flatpak run org.gnome.Calculator 2>&1

10.4 权限审计

10.4.1 权限审计清单

#!/bin/bash
# audit-permissions.sh - Flatpak 应用权限审计

APP_ID="$1"

echo "=== 权限审计报告: $APP_ID ==="
echo "时间: $(date)"
echo ""

# 获取权限信息
PERMS=$(flatpak info --show-permissions "$APP_ID")
META=$(flatpak info --show-metadata "$APP_ID")

echo "--- 1. 网络权限 ---"
if echo "$PERMS" | grep -q "share=network"; then
    echo "  ⚠️ 应用有网络访问权限"
    echo "  审计点: 该应用是否需要网络?"
else
    echo "  ✓ 应用没有网络访问权限"
fi

echo ""
echo "--- 2. 文件系统权限 ---"
if echo "$PERMS" | grep -q "filesystem=host"; then
    echo "  🔴 警告: 应用有 host 文件系统访问权限(高风险)"
elif echo "$PERMS" | grep -q "filesystem=home"; then
    echo "  ⚠️ 应用有 home 目录访问权限"
else
    FS_PERMS=$(echo "$PERMS" | grep "filesystem=")
    if [ -n "$FS_PERMS" ]; then
        echo "  📁 应用有受限文件系统访问:"
        echo "$FS_PERMS" | while read line; do
            echo "    - $line"
        done
    else
        echo "  ✓ 应用没有额外文件系统权限"
    fi
fi

echo ""
echo "--- 3. 设备权限 ---"
if echo "$PERMS" | grep -q "device=all"; then
    echo "  🔴 警告: 应用有所有设备访问权限(高风险)"
elif echo "$PERMS" | grep -q "device=dri"; then
    echo "  ✓ 应用有 GPU 访问权限(正常)"
else
    echo "  ✓ 应用没有额外设备权限"
fi

echo ""
echo "--- 4. D-Bus 权限 ---"
TALK_NAMES=$(echo "$PERMS" | grep "talk-name=")
if [ -n "$TALK_NAMES" ]; then
    echo "  📡 应用可以访问以下 D-Bus 服务:"
    echo "$TALK_NAMES" | while read line; do
        echo "    - $line"
    done
else
    echo "  ✓ 应用没有额外 D-Bus 权限"
fi

echo ""
echo "--- 5. 权限评分 ---"
SCORE=100

if echo "$PERMS" | grep -q "filesystem=host"; then
    SCORE=$((SCORE - 30))
    echo "  扣分: -30 (host 文件系统访问)"
fi
if echo "$PERMS" | grep -q "filesystem=home"; then
    SCORE=$((SCORE - 15))
    echo "  扣分: -15 (home 目录访问)"
fi
if echo "$PERMS" | grep -q "device=all"; then
    SCORE=$((SCORE - 20))
    echo "  扣分: -20 (所有设备访问)"
fi
if echo "$PERMS" | grep -q "share=network"; then
    SCORE=$((SCORE - 5))
    echo "  扣分: -5 (网络访问)"
fi

echo ""
if [ $SCORE -ge 80 ]; then
    echo "  评分: $SCORE/100 ✓ 优秀"
elif [ $SCORE -ge 60 ]; then
    echo "  评分: $SCORE/100 ⚠️ 一般"
else
    echo "  评分: $SCORE/100 🔴 需要审查"
fi

echo ""
echo "=== 审计完成 ==="

10.4.2 使用 Flatseal 进行可视化审计

# 安装 Flatseal
flatpak install flathub com.github.tchx84.Flatseal

# 运行 Flatseal
flatpak run com.github.tchx84.Flatseal

# Flatseal 功能:
# - 查看所有已安装应用的权限
# - 按权限类型筛选
# - 查看权限覆盖历史
# - 重置为默认权限
# - 实时修改权限

10.5 兼容性测试

10.5.1 多发行版测试

#!/bin/bash
# test-multi-distro.sh - 使用 Docker 测试 Flatpak 应用兼容性

APP_BUNDLE="$1"

DISTROS=(
    "fedora:40"
    "ubuntu:24.04"
    "debian:12"
    "archlinux:latest"
)

for distro in "${DISTROS[@]}"; do
    echo "=== 测试 $distro ==="
    
    docker run --rm -it \
        -v "$(pwd)/${APP_BUNDLE}:/app.flatpak:ro" \
        "$distro" \
        bash -c "
            # 安装 flatpak
            if command -v dnf &>/dev/null; then
                dnf install -y flatpak
            elif command -v apt &>/dev/null; then
                apt update && apt install -y flatpak
            elif command -v pacman &>/dev/null; then
                pacman -Sy --noconfirm flatpak
            fi
            
            # 配置 Flathub
            flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
            
            # 安装运行时
            flatpak install -y flathub org.freedesktop.Platform//24.08
            
            # 安装应用
            flatpak install -y /app.flatpak
            
            # 验证安装
            flatpak list --app
            echo '✓ 安装成功'
        "
    
    echo ""
done

10.5.2 桌面环境测试

#!/bin/bash
# test-desktop-envs.sh - 测试不同桌面环境下的 Flatpak 应用

APP_ID="$1"

# 在 GNOME 下测试
echo "=== GNOME 测试 ==="
# 需要在 GNOME 会话中运行
flatpak run "$APP_ID" &
sleep 5
flatpak kill "$APP_ID"
echo "✓ GNOME 测试完成"

# 在 KDE 下测试
echo "=== KDE 测试 ==="
# 需要在 KDE 会话中运行
flatpak run "$APP_ID" &
sleep 5
flatpak kill "$APP_ID"
echo "✓ KDE 测试完成"

10.6 调试技巧

10.6.1 构建调试

# 进入构建失败的模块沙箱
flatpak-builder --build-shell=module-name builddir manifest.json

# 查看构建日志
cat builddir/logs/module-name/build.log

# 在构建环境中运行 shell
flatpak-builder --run builddir manifest.json bash

10.6.2 运行时调试

# 启用详细日志
flatpak run --verbose org.example.App 2>&1 | tee /tmp/flatpak-debug.log

# 查看沙箱挂载信息
flatpak run --command=mount org.example.App

# 查看环境变量
flatpak run --command=env org.example.App | sort

# 进入运行中应用的沙箱
flatpak enter $(flatpak ps --columns=instance | tail -1) bash

10.6.3 日志分析

# Flatpak 自身日志
journalctl --user -f | grep flatpak

# Portal 日志
journalctl --user -f | grep xdg-desktop-portal

# D-Bus 监控
dbus-monitor --session | grep -i flatpak

# 内核沙箱日志(需要 auditd)
ausearch -m AVC -ts recent | grep flatpak

10.6.4 GDB 调试

# 安装 SDK(包含调试工具)
flatpak install flathub org.gnome.Sdk//47

# 以调试模式运行应用
flatpak run --devel --command=bash org.example.App

# 在沙箱中启动 GDB
gdb /app/bin/myapp

# 运行应用
(gdb) run

# 当崩溃发生时
(gdb) bt    # 查看调用栈
(gdb) info locals  # 查看局部变量

10.7 CI/CD 集成

10.7.1 GitHub Actions

# .github/workflows/flatpak.yml
name: Flatpak Build & Test

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  flatpak:
    runs-on: ubuntu-latest
    container:
      image: bilelmoussaoui/flatpak-github-actions:gnome-47
      options: --privileged
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Validate Manifest
      run: |
        jq empty com.example.App.json
        echo "Manifest JSON 语法正确"
    
    - name: Validate AppStream
      run: |
        appstreamcli validate --pedantic com.example.App.metainfo.xml || true
    
    - name: Build Flatpak
      uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v4
      with:
        manifest-path: com.example.App.json
        cache-key: flatpak-builder-${{ github.sha }}
    
    - name: Install & Test
      run: |
        flatpak install --user flatpak-builder/com.example.App.flatpak
        flatpak run com.example.App --version
    
    - name: Permission Audit
      run: |
        flatpak info --show-permissions com.example.App
        echo "权限审计完成"

10.7.2 GitLab CI

# .gitlab-ci.yml
stages:
  - validate
  - build
  - test

validate-manifest:
  stage: validate
  image: python:3.12-slim
  script:
    - pip install yamllint
    - python -m json.tool com.example.App.json
    - echo "Manifest 验证通过"

flatpak-build:
  stage: build
  image: bilelmoussaoui/flatpak-github-actions:gnome-47
  script:
    - flatpak-builder --force-clean --repo=repo builddir com.example.App.json
    - flatpak build-bundle repo com.example.App.flatpak com.example.App stable
  artifacts:
    paths:
      - com.example.App.flatpak
    expire_in: 1 week

flatpak-test:
  stage: test
  image: bilelmoussaoui/flatpak-github-actions:gnome-47
  needs: [flatpak-build]
  script:
    - flatpak install --user com.example.App.flatpak
    - flatpak info --show-permissions com.example.App
    - flatpak run com.example.App --version

10.7.3 自动化测试脚本

#!/bin/bash
# ci-test.sh - CI/CD 测试脚本

set -e

MANIFEST="$1"
APP_ID=$(jq -r '.["app-id"]' "$MANIFEST")

echo "=== CI/CD 测试 ==="
echo "Manifest: $MANIFEST"
echo "App ID: $APP_ID"

# 1. 语法验证
echo ""
echo "--- 1. 语法验证 ---"
jq empty "$MANIFEST"
echo "✓ JSON 语法正确"

# 2. 构建
echo ""
echo "--- 2. 构建 ---"
flatpak-builder \
    --force-clean \
    --disable-download \
    --repo=repo \
    --install-deps-from=flathub \
    builddir \
    "$MANIFEST"
echo "✓ 构建成功"

# 3. 导出
echo ""
echo "--- 3. 导出 ---"
flatpak build-export repo builddir stable
flatpak build-bundle repo "${APP_ID}.flatpak" "$APP_ID" stable
echo "✓ 导出成功"

# 4. 安装
echo ""
echo "--- 4. 安装 ---"
flatpak remote-add --no-gpg-verify --if-not-exists local-repo repo
flatpak install -y local-repo "$APP_ID"
echo "✓ 安装成功"

# 5. 权限检查
echo ""
echo "--- 5. 权限检查 ---"
flatpak info --show-permissions "$APP_ID"

# 6. 运行测试
echo ""
echo "--- 6. 运行测试 ---"
timeout 10 flatpak run "$APP_ID" --version 2>/dev/null && echo "✓ 运行测试通过" || echo "⚠️ 运行测试跳过"

echo ""
echo "=== 所有测试通过 ==="

10.8 业务场景

场景:企业发布审核流程

#!/bin/bash
# release-review.sh - 企业 Flatpak 应用发布审核

APP_ID="$1"
VERSION="$2"

echo "=== 发布审核: $APP_ID v$VERSION ==="

# 1. 验证版本号
echo "1. 验证版本号..."
CURRENT=$(flatpak info --show-metadata "$APP_ID" | grep version | head -1)
echo "   当前版本: $CURRENT"

# 2. 权限审计
echo ""
echo "2. 权限审计..."
flatpak info --show-permissions "$APP_ID" > /tmp/perms.txt
if grep -q "filesystem=host" /tmp/perms.txt; then
    echo "   🔴 审核失败: 存在 host 文件系统访问权限"
    exit 1
fi

# 3. 大小检查
echo ""
echo "3. 大小检查..."
SIZE=$(flatpak info "$APP_ID" | grep "Installed" | awk '{print $2}')
echo "   安装大小: $SIZE"

# 4. 合规检查
echo ""
echo "4. 合规检查..."
echo "   ✓ 权限审计通过"
echo "   ✓ 大小检查通过"

echo ""
echo "=== 审核通过: $APP_ID v$VERSION 可以发布 ==="

10.9 注意事项

⚠️ 沙箱测试环境
建议在虚拟机或容器中进行沙箱测试,避免影响宿主系统。

⚠️ CI/CD 资源
Flatpak 构建可能需要大量内存和磁盘空间。建议 CI/CD 环境至少提供 8 GB RAM 和 50 GB 磁盘。

⚠️ 网络限制
CI/CD 环境可能有网络限制。确保所有依赖源都在 Manifest 中预先声明。


10.10 扩展阅读