feat: B7 APIC/IOAPIC接管 + NVMe写路径 + virtio_net virtqueue + PE兼容层修复 + QEMU测试套件全自动化

中断路由 (B7):
- apic.drv: LAPIC enable + IOAPIC重定向表编程(MADT ISO感知) + apic_route/apic_route_legacy/apic_diag开关
- UTSM: LAPIC EOI钩子(register_apic_eoi, ABI尾部追加) + IDT 0-255全覆盖 + spurious 0xFF特判
- FUCK: apic_route=0默认PIC(零回归) / =1 IOAPIC->LAPIC接管(失败完整降级) / legacy放开IRQ1/12/11

存储:
- nvme.drv: 写路径 + PRP list + 读回校验 + MSI/MSI-X中断驱动(nvme_msi开关, 回退纯轮询)
- build.ps1: apic.drv纳入构建(原为预置二进制)

网络:
- virtio_net: virtqueue RX/TX + netdev注册 + DHCP自测(slirp真实OFFER)

PE兼容层:
- pe_shim: guest->host指针转换(g_emu_mem_base + gp()) + shim fn32列补齐
- gen_pe.py: PE32+ RVA偏置修复(BUG-004) + cmd.exe生成
- desktop_app.h: da_mouse_poll先判AUX位 + da_mouse_init冲刷残留(BUG-003/008)
- shell: Ctrl修饰键跟踪(BUG-001) + g_run_image扩至1MB(BUG-002)

QEMU测试套件:
- 9用例全自动化(Run-All.ps1): boot-regression/driver-matrix/linux-compat/pe-compat/deaicup-e2e/userapps-matrix/network-e2e/performance-baseline/x86emu32-coverage
- QemuTest.ps1公共库: 串口断言/monitor sendkey/screendump/HTTP服务器/并发隔离

文档:
- CLAUDE.md: APIC/IOAPIC能力 + kernel_api暴露 + IRQ后端升级状态
- LESSONS_LEARNED.md: B7 LAPIC/IOAPIC经验教训(2条)
- Bug记录.txt: BUG-20260801-001~008 + BUG-20260803-009(9条)
- test-cases/*.md: 全部补充自动化脚本小节
- 桌面设计.md: .lnk快捷方式 + LINUXAPP.CNF Linux应用图标
- 系统开发策划.md: NVMe/virtio_net标完成 + B2/B7标已解决 + M2进度列
This commit is contained in:
xyou
2026-08-04 18:08:08 +08:00
parent f52157b7af
commit a26866e339
70 changed files with 4652 additions and 504 deletions
+115
View File
@@ -0,0 +1,115 @@
#requires -Version 5.1
<#
.SYNOPSIS
Run-All — Deshab QEMU 自动化测试套件总运行器
.DESCRIPTION
顺序执行 tests/qemu 下全部 9 个用例脚本,每用例独立 powershell 子进程
(独立 QEMU 会话 + firstInit/AUTOEXEC 场景隔离),汇总 PASS/FAIL/耗时。
用例顺序(依赖与耗时综合考虑,快用例靠后):
1. boot-regression.ps1 B1 首启向导 / B2 正常 / B3 dev_mode / B4 无盘 / B5 无 NIC
2. driver-matrix.ps1 16 清单驱动加载矩阵 + NVMe 写路径回归
3. linux-compat.ps1 VMM 分流(QEMU 走 VMM unavailable 优雅降级路径)
4. pe-compat.ps1 PE32+ 原生 + PE32 x86emu32 + 错误路径
5. deaicup-e2e.ps1 Deaicup GUI PE 端到端(首帧像素/键鼠消息/Esc 退出链)
6. userapps-matrix.ps1 shell dev_tests + ping/editor/fileman/browser 工具链
7. network-e2e.ps1 N-A netman 全链路 + N-B curl 主机侧端到端
8. performance-baseline.ps1
启动阶段墙上计时 → .build_tmp/perf_baseline.json
9. x86emu32-coverage.ps1 x86emu32 静态枚举 + hello32 动态解释执行
.PARAMETER Only
只跑指定用例(逗号分隔的脚本基名,大小写不敏感):
.\Run-All.ps1 -Only network-e2e,x86emu32-coverage
.PARAMETER Build
先执行一次 build.ps1(全量编译+打包镜像)再跑用例。
.NOTES
退出码:全部用例 PASS → 0;任一 FAIL → 1。
每用例完整输出存档 .build_tmp\tests\runall\<case>.log。
#>
param(
[string[]]$Only = @(),
[switch]$Build
)
$ErrorActionPreference = 'Stop'
$suiteRoot = $PSScriptRoot
$repoRoot = [System.IO.Path]::GetFullPath((Join-Path $suiteRoot '..\..'))
$outDir = Join-Path $repoRoot '.build_tmp\tests\runall'
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
# ---- 跑前清理:只杀本套件历史会话残留的 QEMU(-name deshabtest- 标记)。
# 绝不全局清理——可能有其他代理/人工在并发跑 QEMU(如 monitor 45454 的调试会话)。
# 本套件所有会话默认 -snapshot(镜像只读),无需靠杀进程解决镜像锁。 ----
. (Join-Path $suiteRoot 'lib\QemuTest.ps1')
Stop-ResidualQemu
# ---- 可选:先构建一次 ----
if ($Build) {
Write-Host '[runall] running build.ps1 ...'
& powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build.ps1')
if ($LASTEXITCODE -ne 0) { Write-Host "[runall] build.ps1 FAILED (exit $LASTEXITCODE)"; exit 1 }
}
$cases = @(
'boot-regression',
'driver-matrix',
'linux-compat',
'pe-compat',
'deaicup-e2e',
'userapps-matrix',
'network-e2e',
'performance-baseline',
'x86emu32-coverage'
)
if ($Only.Count -gt 0) {
# -File 调用下逗号列表常作为单个字符串传入,统一按逗号再切一次
$want = @($Only | ForEach-Object { $_ -split ',' } | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ })
$cases = @($cases | Where-Object { $want -contains $_.ToLower() })
if ($cases.Count -eq 0) { Write-Host "[runall] -Only 过滤后无用例可跑: $($Only -join ',')"; exit 1 }
}
$rows = New-Object System.Collections.ArrayList
$suiteStart = Get-Date
foreach ($case in $cases) {
$script = Join-Path $suiteRoot "$case.ps1"
$log = Join-Path $outDir "$case.log"
Write-Host ''
Write-Host ("================ [runall] {0} ================" -f $case)
$t0 = Get-Date
& powershell -ExecutionPolicy Bypass -File $script 2>&1 | Tee-Object -FilePath $log | Out-Host
$rc = $LASTEXITCODE
$dur = [math]::Round(((Get-Date) - $t0).TotalSeconds, 1)
$outText = ''
if (Test-Path $log) { $outText = Get-Content $log -Raw }
$manualCount = ([regex]::Matches($outText, '\[MANUAL\]')).Count
$skipCount = ([regex]::Matches($outText, '\[SKIP\]')).Count
$status = 'PASS'
if ($rc -ne 0) { $status = 'FAIL' }
[void]$rows.Add([pscustomobject]@{
Case = $case
Status = $status
Duration = "${dur}s"
Manual = $manualCount
Skip = $skipCount
ExitCode = $rc
Log = $log
})
}
# ---- 汇总表 ----
$totalDur = [math]::Round(((Get-Date) - $suiteStart).TotalMinutes, 1)
Write-Host ''
Write-Host ("================ 测试套件汇总 ({0} min) ================" -f $totalDur)
$rows | Format-Table Case, Status, Duration, Manual, Skip, ExitCode -AutoSize | Out-Host
$failCount = @($rows | Where-Object { $_.Status -eq 'FAIL' }).Count
$passCount = @($rows | Where-Object { $_.Status -eq 'PASS' }).Count
Write-Host ("PASS: {0} FAIL: {1} TOTAL: {2}" -f $passCount, $failCount, $rows.Count)
Write-Host ("日志目录: {0}" -f $outDir)
if ($failCount -gt 0) { exit 1 } else { exit 0 }