Flatpak 应用打包完整教程 / 第 10 章:测试与调试
第 10 章:测试与调试
本章目标:掌握 Flatpak 应用的测试方法、调试技巧、权限审计流程和 CI/CD 集成。
10.1 测试策略概述
10.1.1 测试层次
| 层次 | 测试内容 | 工具 |
|---|---|---|
| 构建测试 | Manifest 语法、构建流程 | flatpak-builder、jsonlint |
| 单元测试 | 应用功能测试 | 应用自身的测试框架 |
| 沙箱测试 | 权限、隔离验证 | flatpak run、bwrap |
| 集成测试 | 应用与桌面环境交互 | 虚拟机、容器 |
| 兼容性测试 | 多发行版验证 | 多发行版 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 中预先声明。