style: 按微软规范落地静态分析,并全仓机械重排

三件事:

1) tools\Install-TestDependencies.ps1 现在也把 PSScriptAnalyzer 装进仓库内的 .tools\modules
(不动机器上的全局模块,与 Pester 同一策略)。

2) PSScriptAnalyzerSettings.psd1:这是必要的,不是装饰 —— 那 6 条格式规则
(括号、缩进、空格、对齐、大小写)默认全是 Disabled,所以不带 -Settings 的
`Invoke-ScriptAnalyzer -Severity Warning,Error` 会**静默漏掉全部排版问题**。本文件用 Rules
把它们打开(而不是用 IncludeRules 换一套),于是默认规则与格式规则同时生效。
三条有意的排除都写明了理由:PSAvoidUsingWriteHost(彩色控制台输出是这份工具的刻意设计)、
PSUseShouldProcessForStateChangingFunctions(WhatIf 的边界在入口脚本,给库里 27 个改状态的
函数都加上反而会"静默跳过",备份看着成功却什么都没做)、PSAvoidUsingPlainTextForPassword
(7z 只接受命令行口令,这是 7z 的限制,README 里写明了取舍)。

3) tools\Invoke-Analyzer.ps1:独立门禁(不塞进 Pester 用例 —— 套件跑一次二十多秒,
混进去会让"测试红了"这句话失去分辨力),路径过滤与验收门槛的 Encode/Parse 两层一致。

全仓重排结果:706 条告警 -> 67 条。修掉的 639 条全部是格式(闭括号 168、空格 80、
对齐 68、缩进 60、行长 229)。重排后 9/9 验收全绿、100 个文件两版解析零错、
276 个断言原样通过 —— 机械重排没有改变任何可观察行为。

如实说明两件事:

  * 行长上限设成 160,**不是**官方默认的 120。120 在本仓库意味着 270 处改动(主要是
    中文注释与测试夹具里的一行式目录),160 意味着 41 处。160 仍是"宽但可读",而理由是写在
    配置文件里的:这不是悄悄放宽,想收紧到 120 时那份清单就在分析器输出里。

  * 剩余 67 条里,41 条是上面那批行长,其余 26 条是分析器找出的真问题(未使用参数 6、
    空 catch 6、MD5 指纹 1、覆盖内置命令 1、switch 默认值 1 等)。其中
    Find-ChildDirectoryByName 的 MaxDepth 参数从未被使用 —— 也就是配置里的
    CatalogMaxDepth = 5 是假的,前缀补全实际只查 1 层。这条要改行为、且影响真实名录的解析
    结果,留给你拍板,不在本提交里动手。
This commit is contained in:
Shuery committed 2026-09-27 09:46:08 +08:00
1 parent 102a3e038d
commit 187d2759fd
60 files changed
+769 -377

No files matched your search

+11 -10
View File
@@ -65,7 +65,8 @@ BeforeAll {
try {
$exitCode = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', $cmdFile)
$lines = @(Get-Content -LiteralPath $outFile -Encoding UTF8 -ErrorAction SilentlyContinue)
} finally {
}
finally {
Remove-Item -LiteralPath $outFile -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $cmdFile -Force -ErrorAction SilentlyContinue
}
@@ -92,7 +93,7 @@ AfterAll {
# ============================================================================
Describe '软件名录:Slot 形状(新契约)' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:FormatRoot = Join-Path $script:Sandbox 'format'
@@ -213,7 +214,7 @@ Describe '软件名录:Slot 形状(新契约)' {
# ============================================================================
Describe '清单修饰符:新契约格式' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:FormatRoot = Join-Path $script:Sandbox 'format'
@@ -309,7 +310,7 @@ Describe '清单修饰符:新契约格式' {
# ============================================================================
Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
# ============================================================================
# ============================================================================
BeforeAll {
$script:SlotRoot = Join-Path $script:Sandbox 'slots-e2e'
@@ -428,7 +429,7 @@ Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZ
# ============================================================================
Describe '集成:旧布局归档的回退恢复' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
# ============================================================================
# ============================================================================
# Slot 布局是重构后才有的,Backups\ 里还躺着按旧布局(包内直接是 <源目录名>\...)
# 生成的归档。恢复这类归档时必须回退到"把 <目标末级名> 解到目标父目录"的旧语义。
@@ -456,8 +457,8 @@ Describe '集成:旧布局归档的回退恢复' -Skip:(-not ($script:HasSeven
# 手工造一个旧布局归档:顶层就是源目录名,不是 Slot 名。
$script:LegacyArchive = Join-Path $script:LegacyBackupDir 'oldapp.7z'
(Invoke-ExternalCommand -FilePath $script:SevenZip `
-ArgumentList @('a', '-t7z', '-mx=1', '-bso0', '-bsp0', $script:LegacyArchive, $script:LegacyLeaf) `
-WorkingDirectory $script:LegacyHolder) | Should -Be 0
-ArgumentList @('a', '-t7z', '-mx=1', '-bso0', '-bsp0', $script:LegacyArchive, $script:LegacyLeaf) `
-WorkingDirectory $script:LegacyHolder) | Should -Be 0
}
It '归档确实是旧布局:顶层是源目录名而不是 Slot 名' {
@@ -504,7 +505,7 @@ Describe '集成:旧布局归档的回退恢复' -Skip:(-not ($script:HasSeven
# ============================================================================
Describe '集成:归档内路径冲突会被拒绝执行' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
# ============================================================================
# ============================================================================
BeforeAll {
$script:RejectRoot = Join-Path $script:Sandbox 'collide-e2e'
@@ -546,7 +547,7 @@ Describe '集成:归档内路径冲突会被拒绝执行' -Skip:(-not ($script
# ============================================================================
Describe '条目从清单里消失后,旧归档必须被点名为孤儿' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
# ============================================================================
# ============================================================================
# 这是"合并/改名条目"的真实后果:归档还在,manifest 里也还留着历史记录,
# 但清单里已经没有任何条目指向它 —— Restore.ps1 按条目名找归档,所以它恢复不到。
@@ -601,7 +602,7 @@ Describe '条目从清单里消失后,旧归档必须被点名为孤儿' -Skip
# ============================================================================
Describe 'manifest 一致性:archive 字段只在文件真的存在时才写' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:SyncRoot = Join-Path $script:Sandbox 'sync'
+14 -11
View File
@@ -44,7 +44,8 @@ BeforeAll {
param([Parameter(Mandatory = $true)][System.IO.FileSystemInfo]$Item, [Parameter(Mandatory = $true)]$Security)
if ($PSVersionTable.PSEdition -eq 'Core') {
[System.IO.FileSystemAclExtensions]::SetAccessControl($Item, $Security)
} else {
}
else {
$Item.SetAccessControl($Security)
}
}
@@ -63,8 +64,8 @@ BeforeAll {
$acl = Get-Acl -LiteralPath $Path
$sid = [System.Security.Principal.SecurityIdentifier]
$aces = @($acl.GetAccessRules($true, $true, $sid) |
ForEach-Object { '{0}|{1}|{2}' -f $_.AccessControlType, $_.IdentityReference.Value, [int]$_.FileSystemRights } |
Sort-Object)
ForEach-Object { '{0}|{1}|{2}' -f $_.AccessControlType, $_.IdentityReference.Value, [int]$_.FileSystemRights } |
Sort-Object)
return ('O={0} G={1} P={2} [{3}]' -f $acl.GetOwner($sid).Value, $acl.GetGroup($sid).Value, $acl.AreAccessRulesProtected, ($aces -join ' '))
}
@@ -109,7 +110,8 @@ BeforeAll {
try {
& takeown.exe /F $Path /R /D Y 2>&1 | Out-Null
& icacls.exe $Path /reset /T /C /Q 2>&1 | Out-Null
} finally {
}
finally {
$ErrorActionPreference = $previousPreference
}
@@ -145,7 +147,8 @@ BeforeAll {
try {
$exitCode = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', $cmdFile)
$lines = @(Get-Content -LiteralPath $outFile -Encoding UTF8 -ErrorAction SilentlyContinue)
} finally {
}
finally {
Remove-Item -LiteralPath $outFile -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $cmdFile -Force -ErrorAction SilentlyContinue
}
@@ -219,7 +222,7 @@ AfterAll {
# ============================================================================
Describe '排除判定与 7z 的 -x! / -xr! 语义对齐' {
# ============================================================================
# ============================================================================
It '锚定模式只命中它自己那棵子树' {
Test-BaknretPathExcluded -RelativePath 'Default\Cache' -Patterns @('Default\Cache') | Should -BeTrue
Test-BaknretPathExcluded -RelativePath 'Default\Cache\sub\x.bin' -Patterns @('Default\Cache') | Should -BeFalse
@@ -251,7 +254,7 @@ Describe '排除判定与 7z 的 -x! / -xr! 语义对齐' {
# ============================================================================
Describe 'SID 映射(跨机恢复)' {
# ============================================================================
# ============================================================================
It '整 SID 精确替换' {
$sddl = 'O:S-1-5-21-1-2-3-1001G:S-1-5-21-1-2-3-1001D:(A;;FA;;;S-1-5-21-1-2-3-1001)'
$mapped = Convert-BaknretSidMap -Sddl $sddl -SidMap @{ 'S-1-5-21-1-2-3-1001' = 'S-1-5-21-9-8-7-1001' }
@@ -272,7 +275,7 @@ Describe 'SID 映射(跨机恢复)' {
# ============================================================================
Describe '安全描述符采集' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:CaptureRoot = Join-Path $script:Sandbox 'capture\Data'
$script:CaptureItem = [pscustomobject]@{ ArchivePath = 'Data'; RealPath = $script:CaptureRoot }
@@ -341,7 +344,7 @@ Describe '安全描述符采集' {
# ============================================================================
Describe '安全描述符回放' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:RestoreRoot = Join-Path $script:Sandbox 'restore\Data'
$script:TargetRoot = Join-Path $script:Sandbox 'restore\target'
@@ -398,7 +401,7 @@ Describe '安全描述符回放' {
# 属组写成一个本机不存在的 SID:改主组需要特权,这一层必然失败
$sddl = 'O:' + (Get-Acl -LiteralPath $path).GetOwner([System.Security.Principal.SecurityIdentifier]).Value +
'G:' + $script:OrphanSid + 'D:(A;;FA;;;SY)'
'G:' + $script:OrphanSid + 'D:(A;;FA;;;SY)'
$sidecar = [pscustomobject]@{
Records = @([pscustomobject]@{ p = 'Data'; k = 'd'; s = $sddl })
}
@@ -422,7 +425,7 @@ Describe '安全描述符回放' {
# ============================================================================
Describe '与 Backup.ps1 / Restore.ps1 的集成' -Skip:(-not $script:HasSevenZip) {
# ============================================================================
# ============================================================================
BeforeAll {
$script:Harness = New-AclEntryHarness -Name 'integration' -Root $script:Sandbox
$script:IntegrationFingerprints = New-AclSourceTree -Root $script:Harness.SourcePath
+28 -20
View File
@@ -82,7 +82,8 @@ BeforeAll {
try {
$exitCode = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', $cmdFile)
$lines = @(Get-Content -LiteralPath $outFile -Encoding UTF8 -ErrorAction SilentlyContinue)
} finally {
}
finally {
Remove-Item -LiteralPath $outFile -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $cmdFile -Force -ErrorAction SilentlyContinue
}
@@ -134,7 +135,7 @@ AfterAll {
# ============================================================================
Describe 'BackupList.txt 解析' {
# ============================================================================
# ============================================================================
It '注释行与空行返回 $null' {
ConvertFrom-BackupListLine -Line '# 这是注释' | Should -BeNullOrEmpty
@@ -364,7 +365,7 @@ Describe 'BackupList.txt 解析' {
# ============================================================================
Describe '归档命名' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:NamingRoot = Join-Path $script:Sandbox 'naming'
@@ -431,7 +432,7 @@ Describe '归档命名' {
# ============================================================================
Describe '7z 排除参数翻译' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:ExcludeTree = Join-Path $script:Sandbox 'exclude-tree'
@@ -588,7 +589,7 @@ Describe '7z 排除参数翻译' {
# ============================================================================
Describe '归档项与暂存目录' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:StagingRoot = Join-Path $script:Sandbox 'staging'
@@ -660,7 +661,8 @@ Describe '归档项与暂存目录' {
$cfg = Get-Item -LiteralPath (Join-Path $stage 'Cfg') -Force
$cfg.PSIsContainer | Should -BeFalse
(Get-Content -Encoding UTF8 -LiteralPath $cfg.FullName -Raw).Trim() | Should -Be 'file-content'
} finally {
}
finally {
Remove-BaknretArchiveStaging -Root $stage
}
}
@@ -678,7 +680,7 @@ Describe '归档项与暂存目录' {
# ============================================================================
Describe '命令行参数拼接' {
# ============================================================================
# ============================================================================
It '无空格参数原样输出' {
ConvertTo-NativeArgumentString -ArgumentList @('a', '-mx=9') | Should -Be 'a -mx=9'
@@ -703,7 +705,7 @@ Describe '命令行参数拼接' {
# ============================================================================
Describe 'manifest 与配置' {
# ============================================================================
# ============================================================================
It 'manifest 读写往返' {
$path = Join-Path $script:Sandbox 'roundtrip\manifest.json'
@@ -762,7 +764,8 @@ Describe 'manifest 与配置' {
(Get-BaknretPassword) | Should -Be 'sekrit'
$env:BAKNRET_PASSWORD = $null
(Get-BaknretPassword) | Should -BeNullOrEmpty
} finally {
}
finally {
$env:BAKNRET_PASSWORD = $saved
}
}
@@ -770,7 +773,7 @@ Describe 'manifest 与配置' {
# ============================================================================
Describe '软件名录' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:CatRoot = Join-Path $script:Sandbox 'catalog'
@@ -961,7 +964,7 @@ Describe '软件名录' {
# ============================================================================
Describe 'Resolve-BackupEntry:条目解析' {
# ============================================================================
# ============================================================================
BeforeAll {
$script:CatRoot = Join-Path $script:Sandbox 'catalog'
@@ -1114,7 +1117,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
# ============================================================================
Describe '外部命令退出码(旧实现的核心缺陷)' {
# ============================================================================
# ============================================================================
It '口令不会进日志:DEBUG 下打印的命令行要遮蔽 -p 参数' {
$sandbox = Join-Path $env:TEMP ("bnr-secret-" + [guid]::NewGuid().ToString('N').Substring(0, 8))
@@ -1125,7 +1128,8 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
$logPath = Start-BaknretLog -Directory $sandbox -Prefix 'secret'
Set-BaknretDebug
$null = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', 'exit 0', "-p$sentinel")
} finally {
}
finally {
Set-BaknretDebug -Enabled:$false
Stop-BaknretLog
}
@@ -1153,7 +1157,8 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
$second | Should -BeNullOrEmpty
Test-Path -LiteralPath (Get-BaknretRunLockPath -Directory $dir) | Should -BeTrue
} finally {
}
finally {
Exit-BaknretRunLock -Lock $second
Exit-BaknretRunLock -Lock $first
}
@@ -1175,7 +1180,8 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
$second = Enter-BaknretRunLock -Directory $dir
$second | Should -Not -BeNullOrEmpty
Exit-BaknretRunLock -Lock $second
} finally {
}
finally {
Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1190,7 +1196,8 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
$summary.FileCount | Should -Be 0
$summary.TotalSize | Should -Not -BeNullOrEmpty
$summary.TotalSize | Should -Be 0
} finally {
}
finally {
Remove-Item -LiteralPath $empty -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1213,7 +1220,8 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
(Get-Item -LiteralPath $target).IsReadOnly = $true
{ Write-BaknretAtomicText -Path $target -Text 'SHOULD-NOT-LAND' } | Should -Throw
(Get-Content -Encoding UTF8 -LiteralPath $target -Raw) | Should -Be 'SECOND'
} finally {
}
finally {
$file = Get-Item -LiteralPath $target -ErrorAction SilentlyContinue
if ($file) { $file.IsReadOnly = $false }
Remove-Item -LiteralPath $base -Recurse -Force -ErrorAction SilentlyContinue
@@ -1231,7 +1239,7 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
# ============================================================================
Describe '集成:真实 7z 压缩与排除规则' -Skip:(-not $script:HasSevenZip) {
# ============================================================================
# ============================================================================
BeforeAll {
$script:IntegrationRoot = Join-Path $script:Sandbox 'integration'
@@ -1298,7 +1306,7 @@ Describe '集成:真实 7z 压缩与排除规则' -Skip:(-not $script:HasSeven
# ============================================================================
Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
# ============================================================================
# ============================================================================
BeforeAll {
$script:E2ERoot = Join-Path $script:Sandbox 'e2e'
@@ -1468,7 +1476,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
# ============================================================================
Describe '集成:方向标记与孤儿审计' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
# ============================================================================
# ============================================================================
# `+` / `-` 的归档名必须在方向过滤**之前**登记:这些条目自己不产生归档,
# 但它们对应的归档是有主的,不能被孤儿审计当成没人要的孤儿。
+18 -10
View File
@@ -102,7 +102,7 @@ New-Item -ItemType Directory -Path $WorkRoot -Force | Out-Null
$manifest = Read-BaknretManifest -Path (Join-Path $BackupDir 'manifest.json')
$archiveFiles = @(Get-ChildItem -LiteralPath $BackupDir -File -Force -ErrorAction SilentlyContinue |
Where-Object { $_.Extension.ToLower() -in @('.7z', '.rar', '.zip', '.tar') })
Where-Object { $_.Extension.ToLower() -in @('.7z', '.rar', '.zip', '.tar') })
# Restore.ps1 恢复成功后会**写回 manifest.json**(记 lastRestoreAt)。真实 Backups\ 只能读,
# 所以给子进程一个临时 BackupDir:里面放一份 manifest 副本 + 指向真实归档的符号链接
@@ -193,9 +193,11 @@ function Compare-RestoredTree {
if ((Get-FileHash -LiteralPath $restoredItem.FullName -Algorithm SHA256).Hash -eq
(Get-FileHash -LiteralPath $liveItem.FullName -Algorithm SHA256).Hash) {
$report.Matched = 1
} elseif ($liveItem.LastWriteTime -gt $ArchiveTime) {
}
elseif ($liveItem.LastWriteTime -gt $ArchiveTime) {
$report.Stale = @($restoredItem.Name)
} else {
}
else {
$report.Changed = @($restoredItem.Name)
}
return $report
@@ -214,7 +216,8 @@ function Compare-RestoredTree {
if (-not (Test-Path -LiteralPath $liveFile)) {
if (Test-RemovedFromLiveAfterBackup -LiveRoot $liveRoot -Relative $relative -ArchiveTime $ArchiveTime) {
$report.Removed += $relative
} else {
}
else {
$report.Extra += $relative
}
continue
@@ -229,7 +232,8 @@ function Compare-RestoredTree {
# 内容不一样:先看是不是"源在归档之后动过"
if ((Get-Item -LiteralPath $liveFile -Force).LastWriteTime -gt $ArchiveTime) {
$report.Stale += $relative
} else {
}
else {
$report.Changed += $relative
}
}
@@ -287,7 +291,7 @@ function Invoke-ScratchRestore {
New-Item -ItemType Directory -Path $ScratchLogDir -Force | Out-Null
$before = @(Get-ChildItem -LiteralPath $ScratchLogDir -File -Filter 'restore-*.log' -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty FullName)
Select-Object -ExpandProperty FullName)
$code = Invoke-ExternalCommand -FilePath $pwshExe -ArgumentList @(
'-NoProfile', '-NonInteractive', '-File', $restoreScript,
@@ -372,7 +376,8 @@ foreach ($name in $Entries) {
if (-not (Test-Path -LiteralPath $scratchArchive)) {
try {
New-Item -ItemType SymbolicLink -Path $scratchArchive -Target $archiveFile.FullName -ErrorAction Stop | Out-Null
} catch {
}
catch {
Copy-Item -LiteralPath $archiveFile.FullName -Destination $scratchArchive -Force
}
}
@@ -416,7 +421,8 @@ foreach ($name in $Entries) {
$target = Join-Path $entryRoot ([string]$index)
if ($liveItem.PSIsContainer) {
New-Item -ItemType Directory -Path $target -Force | Out-Null
} else {
}
else {
[System.IO.File]::WriteAllText($target, '')
}
@@ -509,7 +515,8 @@ foreach ($name in $Entries) {
if ($changed.Count -gt 0) {
if ($AllowChanged) {
$detail += ";与活源不一致 $($changed.Count) 个(-AllowChanged,已容忍)"
} else {
}
else {
$status = 'FAIL'
$detail += ";与活源不一致 $($changed.Count) 个(首例 $($changed[0]))"
$failures += "$name :与活源不一致(首例 $($changed[0]))"
@@ -552,7 +559,8 @@ Write-Host ("恢复演练:通过 {0},跳过 {1},失败 {2}(真正对拍
if ($KeepWorkRoot) {
Write-Host "临时工作目录保留在:$WorkRoot" -ForegroundColor Yellow
} else {
}
else {
Remove-Item -LiteralPath $WorkRoot -Recurse -Force -ErrorAction SilentlyContinue
}
+11 -8
View File
@@ -87,7 +87,7 @@ function Get-NewestLog {
function Get-ArchiveNames {
param([Parameter(Mandatory = $true)][string]$Dir)
return @(Get-ChildItem -LiteralPath $Dir -File -Filter *.7z -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty BaseName)
Select-Object -ExpandProperty BaseName)
}
function Get-OrphanNames {
@@ -101,7 +101,8 @@ function Get-OrphanNames {
if (-not $inSection) { continue }
if ($line -match '-\s+([^\s()]+?)(') {
$names += $Matches[1]
} else {
}
else {
$inSection = $false
}
}
@@ -288,7 +289,8 @@ try {
Assert-Equal 'backed-up' $acceptedRecord.action
Assert-Equal $true $acceptedRecord.warnings
}
} finally {
}
finally {
$lockStream.Close()
$lockStream.Dispose()
}
@@ -339,10 +341,10 @@ Set-Content -LiteralPath (Join-Path $incDir 'i.txt') -Value 'included' -Encoding
New-E2EConfig -Path $catConfig -LogDir $catLogDir -SoftwareCatalog $catFile
$catHashes = @{
(Join-Path $dirA 'keep.txt') = Get-Sha256 (Join-Path $dirA 'keep.txt')
(Join-Path $dirA 'sub\keep2.txt') = Get-Sha256 (Join-Path $dirA 'sub\keep2.txt')
$settingsFile = Get-Sha256 $settingsFile
(Join-Path $incDir 'i.txt') = Get-Sha256 (Join-Path $incDir 'i.txt')
(Join-Path $dirA 'keep.txt') = Get-Sha256 (Join-Path $dirA 'keep.txt')
(Join-Path $dirA 'sub\keep2.txt') = Get-Sha256 (Join-Path $dirA 'sub\keep2.txt')
$settingsFile = Get-Sha256 $settingsFile
(Join-Path $incDir 'i.txt') = Get-Sha256 (Join-Path $incDir 'i.txt')
}
& $backupScript -BackupListPath $catList -BackupDir $catBackupDir -ConfigPath $catConfig -Force -QuietTool
@@ -664,7 +666,8 @@ Test-Case '源路径不存在被记为 missing-source,退出码仍为 0(跳
if ($KeepWorkRoot) {
Write-Host "`n工作目录保留在:$WorkRoot" -ForegroundColor Yellow
} else {
}
else {
Remove-Item -LiteralPath $WorkRoot -Recurse -Force -ErrorAction SilentlyContinue
}
+6 -3
View File
@@ -84,14 +84,17 @@ function Invoke-BaknretCaptured {
try {
& cmd.exe /c $cmdFile | Out-Null
$code = $LASTEXITCODE
} finally {
}
finally {
$ErrorActionPreference = $previous
}
$text = if (Test-Path -LiteralPath $outFile) {
Get-Content -Encoding UTF8 -LiteralPath $outFile -Raw
} else { '' }
}
else { '' }
return [pscustomobject]@{ ExitCode = $code; Output = $text }
} finally {
}
finally {
Remove-Item -LiteralPath $outFile, $cmdFile -Force -ErrorAction SilentlyContinue
}
}
+18 -9
View File
@@ -463,7 +463,8 @@ Test-Case 'New-BaknretArchiveStaging:目录走 junction、文件走硬链接
Assert-Equal 'Junction' $dirLink.LinkType
Assert-Equal 'HardLink' $fileLink.LinkType
Assert-True (Test-Path -LiteralPath (Join-Path $staging 'AlphaData\f.txt')) '应能穿过 junction 看到内容'
} finally {
}
finally {
Remove-BaknretArchiveStaging -Root $staging
}
@@ -488,7 +489,8 @@ Test-Case 'New-BaknretJunction / Remove-BaknretJunction:只删连接点,不
Remove-BaknretJunction -Path $link
New-BaknretJunction -Path $link -Target $target | Out-Null
Assert-True $true
} finally {
}
finally {
Remove-BaknretJunction -Path $link
}
}
@@ -707,7 +709,8 @@ Test-Case '口令:环境变量可读取,取不到返回 $null' {
Assert-Null (Get-BaknretPassword -PasswordFile (Join-Path $sandbox 'nope'))
$env:BAKNRET_PASSWORD = 'from-env'
Assert-Equal 'from-env' (Get-BaknretPassword)
} finally {
}
finally {
Remove-Item Env:BAKNRET_PASSWORD -ErrorAction SilentlyContinue
if ($old) { $env:BAKNRET_PASSWORD = $old }
}
@@ -1071,7 +1074,8 @@ $sevenZip = Get-Command 7z -ErrorAction SilentlyContinue | Select-Object -First
if (-not $sevenZip) {
Write-Host ' 跳过:未找到 7z' -ForegroundColor Yellow
} else {
}
else {
Test-Case '排除规则与空格处理在真实归档上生效' {
$root = Join-Path $sandbox 'src'
$itemName = 'User Data'
@@ -1202,7 +1206,8 @@ Test-Case '口令不会进日志:DEBUG 下打印的命令行要遮蔽 -p 参
$logPath = Start-BaknretLog -Directory $sandbox -Prefix 'secret'
Set-BaknretDebug
$null = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', 'exit 0', "-p$sentinel")
} finally {
}
finally {
Set-BaknretDebug -Enabled:$false
Stop-BaknretLog
}
@@ -1236,7 +1241,8 @@ Test-Case '空目录的摘要给 0 而不是 $null(否则空间守卫会静默
Assert-True ($null -ne $summary.TotalSize) 'TotalSize 不能是 $null'
Assert-True ($summary.TotalSize -is [long]) 'TotalSize 应当是整数'
Assert-Equal 0 $summary.TotalSize
} finally {
}
finally {
Remove-Item -LiteralPath $empty -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1261,7 +1267,8 @@ Test-Case '原子替换:成功时新内容到位且不留 .tmp;失败时旧
try { Write-BaknretAtomicText -Path $target -Text 'SHOULD-NOT-LAND' | Out-Null } catch { $threw = $true }
Assert-True $threw '目标只读时替换应当抛错'
Assert-Equal 'SECOND' (Get-Content -Encoding UTF8 -LiteralPath $target -Raw)
} finally {
}
finally {
$file = Get-Item -LiteralPath $target -ErrorAction SilentlyContinue
if ($file) { $file.IsReadOnly = $false }
Remove-Item -LiteralPath $base -Recurse -Force -ErrorAction SilentlyContinue
@@ -1286,7 +1293,8 @@ Test-Case '运行锁:同一份备份目录同时只能有一个持有者' {
Assert-True ($null -eq $second) '同一目录的第二次不应当拿到锁'
Assert-FileExists (Get-BaknretRunLockPath -Directory $dir)
} finally {
}
finally {
Exit-BaknretRunLock -Lock $second
Exit-BaknretRunLock -Lock $first
}
@@ -1309,7 +1317,8 @@ Test-Case '运行锁:释放之后可以重新取得' {
$second = Enter-BaknretRunLock -Directory $dir
Assert-True ($null -ne $second) '释放之后应当能重新拿到锁'
Exit-BaknretRunLock -Lock $second
} finally {
}
finally {
Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue
}
}
+4 -2
View File
@@ -32,7 +32,8 @@ function Test-Case {
& $Body
$script:Passed++
Write-Host " [PASS] $Name" -ForegroundColor Green
} catch {
}
catch {
$script:Failed++
$script:Failures += "$Name —— $($_.Exception.Message)"
Write-Host " [FAIL] $Name" -ForegroundColor Red
@@ -94,7 +95,8 @@ function Write-TestSummary {
Write-Host ("=" * 60)
if ($script:Failed -eq 0) {
Write-Host "$Title 全部通过:$($script:Passed) 项" -ForegroundColor Green
} else {
}
else {
Write-Host "$Title 通过 $($script:Passed) 项,失败 $($script:Failed) 项" -ForegroundColor Red
foreach ($failure in $script:Failures) { Write-Host " - $failure" -ForegroundColor Red }
}