docs(report): 新增 PROJECT_REPORT.md 与 CI/CD 流水线记录
PROJECT_REPORT.md:论文式项目报告(摘要 / 目录 / 7 章 / 参考文献 / 致谢 / 附录 A),
4 张 Mermaid 图同样经真实浏览器渲染验证。所有数字都与证据文件逐个核对过。
.scratch/ci-cd/:本次流水线的全部证据与可重跑脚本
* 阶段报告:依赖安全扫描 / 许可证合规 / 阶段 0 静态分析 / 阶段 3 测试与性能
* 证据:分析器原始输出(修复前 84 条、修复后 80 条)、Pester 详细输出、
性能基线 JSON、黑盒用例结果(阶段 1 的 11 例与阶段 2 回归的 12 例)
* 可重跑:blackbox-tests.ps1 / blackbox-regression.ps1 / perf-baseline.ps1
两条边界必须写在明处,不能含糊:
* **VM 内验证只取到修复前的快照**(Pester 183 项全绿)。随后会话审批策略改为 never,
gsudo 提权被自动拒绝(退出码 999),而 Hyper-V 与 PowerShell Direct 都需要管理员,
于是修复后的三套件在 VM 内**没有跑成**。报告里明确标注了范围,没有把"宿主跑绿了"
说成"VM 也跑绿了"。
* **性能基线是首次建立**,无历史可比,故无退化可判;指标只在同机同宿主下对比,
跨机比数字没有意义。
另外记一笔:静态分析的口径是"仓库自己的门禁"。剩余 80 条全是风格类(0 Error),且逐条
有依据 —— 行长 160 是配置里写明的有意偏离,PSPlaceCloseBrace 等集中在测试夹具字符串内
(改了会改变断言语义)。严格模式的"零容忍"在这里与仓库自身约定相抵,选择尊重仓库约定
并在报告里登记,而不是制造一个横跨 12 个文件的纯排版大 diff。
This commit is contained in:
1 parent
f4729baca7
commit
45bbd66815
18 files changed
+2974
No files matched your search
@@ -0,0 +1,171 @@
|
||||
# 阶段 2 回归:把阶段 1 的 11 条黑盒用例按**修正后的前置条件**重跑一遍。
|
||||
# 修正点:
|
||||
# * BB-11 原用 -DryRun,而孤儿审计只在真实运行里报告 -> 改成真实运行(原用例的夹具错误,不是产品缺陷)
|
||||
# * BB-06 的期望不变(显式清单不存在必须非 0),验证修复是否生效
|
||||
param([string]$OutJson = "$PSScriptRoot\blackbox_regression.json")
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$repo = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||
$host7 = (Get-Command pwsh).Source
|
||||
$host51 = (Get-Command powershell).Source
|
||||
$zip = (Get-Command 7z -ErrorAction SilentlyContinue).Source
|
||||
if (-not $zip) { foreach ($p in 'C:\Programs\Scoop\shims\7z.exe') { if (Test-Path $p) { $zip = $p } } }
|
||||
|
||||
$sandbox = Join-Path $env:TEMP ('baknret-reg-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
$src = Join-Path $sandbox 'srcdocs'
|
||||
New-Item -ItemType Directory -Path (Join-Path $src 'nested') -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $src 'a.txt') -Value 'alpha' -Encoding utf8
|
||||
Set-Content -LiteralPath (Join-Path $src 'nested\b.txt') -Value 'beta' -Encoding utf8
|
||||
Set-Content -LiteralPath (Join-Path $src 'nested\skipme.log') -Value 'noise' -Encoding utf8
|
||||
|
||||
$listPath = Join-Path $sandbox 'BackupList.txt'
|
||||
Set-Content -LiteralPath $listPath -Encoding utf8 -Value @(
|
||||
"$src :- 'nested\skipme.log'",
|
||||
'+ ' + (Join-Path $sandbox 'nonexistent')
|
||||
)
|
||||
$backupDir = Join-Path $sandbox 'Backups'
|
||||
|
||||
$cases = [System.Collections.Generic.List[object]]::new()
|
||||
function Add-Case {
|
||||
param([string]$Id, [string]$Title, [string]$Expected, [string]$Actual, [bool]$Pass, [string]$Note = '')
|
||||
$cases.Add([pscustomobject]@{ Id = $Id; Title = $Title; Expected = $Expected; Actual = $Actual
|
||||
Status = $(if ($Pass) { 'PASS' } else { 'FAIL' }); Note = $Note })
|
||||
}
|
||||
|
||||
function Invoke-Bak {
|
||||
param([string]$HostName, [string[]]$Arguments, [string]$Script = 'Backup-Data.ps1')
|
||||
$exe = if ($HostName -eq '7') { $host7 } else { $host51 }
|
||||
$all = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', (Join-Path $repo $Script)) + $Arguments
|
||||
$out = & $exe @all 2>&1
|
||||
return [pscustomobject]@{ ExitCode = $LASTEXITCODE; Output = @($out) }
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- BB-01 真实备份
|
||||
$r = Invoke-Bak -HostName 7 -Arguments @('-BackupListPath', $listPath, '-BackupDir', $backupDir)
|
||||
$archives = @(Get-ChildItem -LiteralPath $backupDir -Filter '*.7z' -ErrorAction SilentlyContinue)
|
||||
Add-Case -Id 'BB-01' -Title '真实备份:退出码 0 + 产出归档 + manifest' `
|
||||
-Expected '退出码 0;≥1 个 .7z;manifest.json 存在' `
|
||||
-Actual ("退出码={0};归档={1}" -f $r.ExitCode, $archives.Count) `
|
||||
-Pass ($r.ExitCode -eq 0 -and $archives.Count -ge 1 -and (Test-Path (Join-Path $backupDir 'manifest.json')))
|
||||
|
||||
# ---------------------------------------------------------------- BB-02 排除生效
|
||||
$listing = if ($archives.Count -gt 0 -and $zip) { @(& $zip l -ba $archives[0].FullName 2>&1) } else { @() }
|
||||
$hasSkip = @($listing | Where-Object { $_ -match 'skipme\.log' }).Count -gt 0
|
||||
$hasKeep = @($listing | Where-Object { $_ -match 'a\.txt|b\.txt' }).Count -gt 0
|
||||
Add-Case -Id 'BB-02' -Title '排除模式真的把内容挡在归档之外' `
|
||||
-Expected '归档内无 skipme.log,有 a.txt/b.txt' `
|
||||
-Actual ("skipme={0};keep={1}" -f $hasSkip, $hasKeep) -Pass ($hasKeep -and -not $hasSkip)
|
||||
|
||||
# ---------------------------------------------------------------- BB-03 干跑不写盘
|
||||
$h1 = (Get-FileHash -LiteralPath (Join-Path $backupDir 'manifest.json') -Algorithm SHA256).Hash
|
||||
$r = Invoke-Bak -HostName 7 -Arguments @('-DryRun', '-BackupListPath', $listPath, '-BackupDir', $backupDir)
|
||||
$h2 = (Get-FileHash -LiteralPath (Join-Path $backupDir 'manifest.json') -Algorithm SHA256).Hash
|
||||
Add-Case -Id 'BB-03' -Title '-DryRun 一个字节都不写' `
|
||||
-Expected '退出码 0;manifest SHA256 前后一致' `
|
||||
-Actual ("退出码={0};哈希一致={1}" -f $r.ExitCode, ($h1 -eq $h2)) -Pass ($r.ExitCode -eq 0 -and $h1 -eq $h2)
|
||||
|
||||
# ---------------------------------------------------------------- BB-04 missing-source
|
||||
$actions = @((Get-Content (Join-Path $backupDir 'manifest.json') -Raw | ConvertFrom-Json).items.PSObject.Properties.Value.action)
|
||||
Add-Case -Id 'BB-04' -Title '源不存在记 missing-source,不算失败' `
|
||||
-Expected '含 missing-source;退出码 0' `
|
||||
-Actual ("actions={0};退出码={1}" -f ($actions -join ','), $r.ExitCode) `
|
||||
-Pass ($actions -contains 'missing-source' -and $r.ExitCode -eq 0)
|
||||
|
||||
# ---------------------------------------------------------------- BB-05 -Only
|
||||
$r = Invoke-Bak -HostName 7 -Arguments @('-DryRun', '-Only', 'srcdocs', '-BackupListPath', $listPath, '-BackupDir', $backupDir)
|
||||
$hitMissing = @($r.Output | Where-Object { $_ -match 'nonexistent' }).Count -gt 0
|
||||
Add-Case -Id 'BB-05' -Title '-Only 只处理匹配的条目' `
|
||||
-Expected '退出码 0;输出不提未选中的 nonexistent' `
|
||||
-Actual ("退出码={0};提到 nonexistent={1}" -f $r.ExitCode, $hitMissing) -Pass ($r.ExitCode -eq 0 -and -not $hitMissing)
|
||||
|
||||
# ---------------------------------------------------------------- BB-06 ★ 修复验证
|
||||
$ghost = Join-Path $sandbox 'typo-in-path.txt'
|
||||
$r = Invoke-Bak -HostName 7 -Arguments @('-BackupListPath', $ghost, '-BackupDir', $backupDir)
|
||||
$created = Test-Path -LiteralPath $ghost
|
||||
Add-Case -Id 'BB-06' -Title '★显式指定的清单不存在 -> 必须报失败(本次修复点)' `
|
||||
-Expected '退出码 ≠ 0;不创建模板;输出含"指定的清单不存在"' `
|
||||
-Actual ("退出码={0};误建模板={1};提示={2}" -f $r.ExitCode, $created, (@($r.Output | Where-Object { $_ -match '指定的清单不存在' }).Count -gt 0)) `
|
||||
-Pass ($r.ExitCode -ne 0 -and -not $created -and (@($r.Output | Where-Object { $_ -match '指定的清单不存在' }).Count -gt 0)) `
|
||||
-Note '阶段 1 原为 FAIL(退出码 0 且建了模板),本次修复后应转为 PASS'
|
||||
|
||||
# ---------------------------------------------------------------- BB-06b 对照:首次运行引导仍在
|
||||
$realList = Join-Path $repo 'BackupList.txt'
|
||||
$parked = Join-Path $sandbox 'BackupList.parked'
|
||||
$guideDir = Join-Path $sandbox 'guide'
|
||||
New-Item -ItemType Directory -Path $guideDir -Force | Out-Null
|
||||
$r = $null
|
||||
try {
|
||||
Move-Item -LiteralPath $realList -Destination $parked -Force
|
||||
$r = Invoke-Bak -HostName 7 -Arguments @('-BackupDir', (Join-Path $guideDir 'Backups'))
|
||||
$regenerated = Test-Path -LiteralPath $realList
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $parked) { Move-Item -LiteralPath $parked -Destination $realList -Force }
|
||||
}
|
||||
Add-Case -Id 'BB-06b' -Title '对照:未显式指定时首次运行仍建模板并退出 0' `
|
||||
-Expected '退出码 0;模板被创建;随后仓库原文件已还原' `
|
||||
-Actual ("退出码={0};模板创建={1};仓库还原={2}" -f $r.ExitCode, $regenerated, (Test-Path -LiteralPath $realList)) `
|
||||
-Pass ($r.ExitCode -eq 0 -and $regenerated -and (Test-Path -LiteralPath $realList))
|
||||
|
||||
# ---------------------------------------------------------------- BB-07 垫片转发
|
||||
$r = Invoke-Bak -HostName 7 -Arguments @('-DryRun', '-BackupListPath', $listPath, '-BackupDir', $backupDir) -Script 'Backup.ps1'
|
||||
$said = @($r.Output | Where-Object { $_ -match '已改名为' }).Count -gt 0
|
||||
Add-Case -Id 'BB-07' -Title '旧名字垫片转发且退出码原样传递' `
|
||||
-Expected '退出码 0;有改名提示' `
|
||||
-Actual ("退出码={0};提示={1}" -f $r.ExitCode, $said) -Pass ($r.ExitCode -eq 0 -and $said)
|
||||
|
||||
# ---------------------------------------------------------------- BB-08 双宿主
|
||||
$r7 = Invoke-Bak -HostName 7 -Arguments @('-DryRun', '-BackupListPath', $listPath, '-BackupDir', $backupDir)
|
||||
$r51 = Invoke-Bak -HostName '5.1' -Arguments @('-DryRun', '-BackupListPath', $listPath, '-BackupDir', $backupDir)
|
||||
Add-Case -Id 'BB-08' -Title '5.1 与 7.x 行为一致' `
|
||||
-Expected '两个宿主退出码都是 0' `
|
||||
-Actual ("7={0};5.1={1}" -f $r7.ExitCode, $r51.ExitCode) -Pass ($r7.ExitCode -eq 0 -and $r51.ExitCode -eq 0)
|
||||
|
||||
# ---------------------------------------------------------------- BB-09 特殊字符
|
||||
$sp = Join-Path $sandbox 'sp&chars#dir'
|
||||
New-Item -ItemType Directory -Path $sp -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $sp 'x.txt') -Value 'special' -Encoding utf8
|
||||
$list2 = Join-Path $sandbox 'BackupList2.txt'
|
||||
Set-Content -LiteralPath $list2 -Encoding utf8 -Value "`"$sp`""
|
||||
$bd2 = Join-Path $sandbox 'Backups2'
|
||||
$r = Invoke-Bak -HostName 7 -Arguments @('-BackupListPath', $list2, '-BackupDir', $bd2)
|
||||
$n = @(Get-ChildItem -LiteralPath $bd2 -Filter '*.7z' -ErrorAction SilentlyContinue).Count
|
||||
Add-Case -Id 'BB-09' -Title '含 & 与 # 的路径(整行引号)' `
|
||||
-Expected '退出码 0 且产出归档' `
|
||||
-Actual ("退出码={0};归档={1}" -f $r.ExitCode, $n) -Pass ($r.ExitCode -eq 0 -and $n -ge 1)
|
||||
|
||||
# ---------------------------------------------------------------- BB-10 无控制台不挂起
|
||||
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$pinfo.FileName = $host7
|
||||
$pinfo.Arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$repo\Manage-Backup.ps1`""
|
||||
$pinfo.RedirectStandardOutput = $true; $pinfo.RedirectStandardError = $true
|
||||
$pinfo.UseShellExecute = $false; $pinfo.CreateNoWindow = $true
|
||||
$proc = [System.Diagnostics.Process]::Start($pinfo)
|
||||
$finished = $proc.WaitForExit(60000)
|
||||
if (-not $finished) { try { $proc.Kill() } catch {} }
|
||||
Add-Case -Id 'BB-10' -Title '无控制台且无 -InputScript 时报错退出,绝不挂起' `
|
||||
-Expected '60 秒内退出且退出码 2' `
|
||||
-Actual ("已退出={0};退出码={1}" -f $finished, $proc.ExitCode) -Pass ($finished -and $proc.ExitCode -eq 2)
|
||||
|
||||
# ---------------------------------------------------------------- BB-11 ★ 修正夹具:真实运行
|
||||
$orphanDir = Join-Path $sandbox 'Backups3'
|
||||
New-Item -ItemType Directory -Path $orphanDir -Force | Out-Null
|
||||
if ($archives.Count -gt 0) { Copy-Item -LiteralPath $archives[0].FullName -Destination (Join-Path $orphanDir 'GhostSoftware.7z') }
|
||||
$r = Invoke-Bak -HostName 7 -Arguments @('-BackupListPath', $listPath, '-BackupDir', $orphanDir)
|
||||
$named = @($r.Output | Where-Object { $_ -match 'GhostSoftware' }).Count -gt 0
|
||||
Add-Case -Id 'BB-11' -Title '★孤儿归档审计(改用真实运行)' `
|
||||
-Expected '输出点名 GhostSoftware.7z' `
|
||||
-Actual ("退出码={0};点名={1}" -f $r.ExitCode, $named) -Pass $named `
|
||||
-Note '阶段 1 的 FAIL 系夹具错误:孤儿审计只在真实运行里报告,干跑不报'
|
||||
|
||||
$report = [ordered]@{
|
||||
testedAt = (Get-Date).ToString('s')
|
||||
total = $cases.Count
|
||||
passed = @($cases | Where-Object Status -eq 'PASS').Count
|
||||
failed = @($cases | Where-Object Status -eq 'FAIL').Count
|
||||
cases = $cases
|
||||
}
|
||||
$report | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $OutJson -Encoding utf8
|
||||
$cases | Format-Table Id, Status, Title -AutoSize
|
||||
Write-Host ("回归合计 {0};通过 {1};失败 {2}" -f $report.total, $report.passed, $report.failed)
|
||||
Write-Host ("沙盒: " + $sandbox)
|
||||
Reference in new issue
Block a user