Files
BakNRet/tests/TestHelpers.psm1
T
Shuery dbc0c00554 重构为可核对、可恢复的备份工具(P0-P3)
修复(P0)
- 退出码:改用 .NET Process 继承控制台启动外部命令。Start-Process -PassThru 的
  ExitCode 在 PowerShell 7.7.0-preview.4 上恒为 $null,会把成功的压缩判成失败,
  并让 "exit 2 -> 删档重试" 的自愈分支永远不可达。
- BackupList.txt 解析:先按第一个 :: 切开再处理引号,修正整行被引号包住时
  排除表被吞进路径的问题(该条目此前被静默跳过,其 2.8 GB 归档成了孤儿)。
- 排除分隔符同时接受 , 与 ;:此前解析器只认 ; 而清单里写的是 ,,
  等于所有排除规则都没生效。
- 7z 排除参数不再嵌引号,含空格的模式自动转成 ?:旧写法 -x!"路径" 会让引号
  成为模式的一部分,导致排除对所有条目都失效。

加固(P1)
- 先写临时归档 -> 7z t 校验 -> 原子替换,中断不再污染正式归档。
- 放弃 7z 的更新模式 u:固实压缩下收益极小,却让排除规则改动与已删文件
  永远进不了归档。
- 新增 Backups/manifest.json 与 logs/*.log,跳过/失败有据可查。
- 结尾按失败数 exit;恢复支持 -WhatIf / -DryRun / -VerifyOnly / -Only。
- 恢复优先用 manifest 定位归档,并精确比较 BaseName(不再用 -Filter 通配)。
- 修正 tar 分支用 $LASTEXITCODE 判断成功与否的缺陷。
- 有警告(文件被占用)时拒绝用不完整的归档覆盖完整归档,需显式 -AcceptWarnings。

策略与安全(P2)
- Edge 条目加排除规则:解压后 4.22 GB 中 3.79 GB 是可再生的缓存/遥测/扩展本体,
  保留书签、密码、偏好、历史与站点数据。
- 可选 7z 加密(@encrypt 标记或全局开关),取不到口令时明确失败,绝不写明文。
- 磁盘空间守卫:放不下就跳过该条目,低于阈值告警。

工程化(P3)
- 新增 BackupConfig.psd1、README.md、.gitignore。
- tests/Run-Tests.ps1(32 项)与 tests/Run-E2E.ps1(16 项端到端验收)。
- tools/Register-BackupTask.ps1 注册每日计划任务。
- 归档命名算法保持不变,已有归档不会失联。
2026-09-21 20:10:18 +08:00

112 lines
3.4 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
BakNRet 测试用的极简断言运行器(零依赖,PS 5.1 / 7.x 通用)。
.DESCRIPTION
不用 Pester:本机只装了 3.4.0,缺少 `Should -Be` 这类 Pester 4+ 语法,
为跑几个测试去装 Pester 5 不划算。这里只需要 Test-Case + Assert-* 就够。
#>
$script:Passed = 0
$script:Failed = 0
$script:Failures = @()
function Reset-TestResult {
$script:Passed = 0
$script:Failed = 0
$script:Failures = @()
}
function Get-TestResult {
return [pscustomobject]@{
Passed = $script:Passed
Failed = $script:Failed
Failures = @($script:Failures)
}
}
function Test-Case {
param([string]$Name, [scriptblock]$Body)
try {
& $Body
$script:Passed++
Write-Host " [PASS] $Name" -ForegroundColor Green
} catch {
$script:Failed++
$script:Failures += "$Name —— $($_.Exception.Message)"
Write-Host " [FAIL] $Name" -ForegroundColor Red
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkGray
}
}
function Assert-Equal {
param($Expected, $Actual, [string]$Because)
if ($Expected -ne $Actual) {
throw ("期望 [{0}],实际 [{1}]{2}" -f $Expected, $Actual, $(if ($Because) { "($Because)" } else { '' }))
}
}
function Assert-True {
param($Condition, [string]$Because)
if (-not $Condition) { throw ("断言为真失败{0}" -f $(if ($Because) { ":$Because" } else { '' })) }
}
function Assert-False {
param($Condition, [string]$Because)
if ($Condition) { throw ("断言为假失败{0}" -f $(if ($Because) { ":$Because" } else { '' })) }
}
function Assert-Null {
param($Value, [string]$Because)
if ($null -ne $Value) {
throw ("期望为 null,实际 [{0}]{1}" -f $Value, $(if ($Because) { "($Because)" } else { '' }))
}
}
function Assert-FileExists {
param([string]$Path, [string]$Because)
if (-not (Test-Path -LiteralPath $Path)) {
throw ("文件应当存在但不存在:{0}{1}" -f $Path, $(if ($Because) { "($Because)" } else { '' }))
}
}
function Assert-FileMissing {
param([string]$Path, [string]$Because)
if (Test-Path -LiteralPath $Path) {
throw ("文件不应存在但存在:{0}{1}" -f $Path, $(if ($Because) { "($Because)" } else { '' }))
}
}
function Assert-FileHashEqual {
param([string]$ExpectedPath, [string]$ActualPath)
Assert-FileExists $ExpectedPath
Assert-FileExists $ActualPath
$a = (Get-FileHash -LiteralPath $ExpectedPath -Algorithm SHA256).Hash
$b = (Get-FileHash -LiteralPath $ActualPath -Algorithm SHA256).Hash
if ($a -ne $b) { throw ("内容不一致:{0} vs {1}" -f $ExpectedPath, $ActualPath) }
}
function Write-TestSummary {
param([string]$Title = '测试')
Write-Host ""
Write-Host ("=" * 60)
if ($script:Failed -eq 0) {
Write-Host "$Title 全部通过:$($script:Passed) 项" -ForegroundColor Green
} else {
Write-Host "$Title 通过 $($script:Passed) 项,失败 $($script:Failed) 项" -ForegroundColor Red
foreach ($failure in $script:Failures) { Write-Host " - $failure" -ForegroundColor Red }
}
Write-Host ("=" * 60)
return $script:Failed
}
Export-ModuleMember -Function @(
'Reset-TestResult', 'Get-TestResult', 'Test-Case',
'Assert-Equal', 'Assert-True', 'Assert-False', 'Assert-Null',
'Assert-FileExists', 'Assert-FileMissing', 'Assert-FileHashEqual',
'Write-TestSummary'
)