refactor: 公共面补 BakNRet 前缀,产品名大小写全仓统一
16 个没有前缀的公共函数补上 BakNRet(Write-Log → Write-BakNRetLog、Resolve-BackupEntry → Resolve-BakNRetBackupEntry、Find-ChildDirectoryByName → Find-BakNRetChildDirectoryByName 等), 另外把全仓的 Baknret 统一成 BakNRet(47 个文件、940 处、65 个定义文件重命名)。 这不是审美问题:静态分析直接拓出一条实据 —— Write-Log 与本机某个已装模块导出的命令 **重名**(PSAvoidOverwritingBuiltInCmdlets),而重名的后果是导入两个模块时有一方的命令被 静默遮蔽。补前缀正是这条规则的解法,改名后它归零。 为什么敢做这个规模:PowerShell 的函数名解析大小写不敏感,所以 Baknret → BakNRet 在功能 上是零风险;真正要验证的是 16 个补前缀的调用点,而 276 个断言几乎覆盖了每个函数。另外 "名字与文件名一致"这条不变式有断言盯着(加载器点源的文件集合 vs 磁盘)。 踩到并记下的坑:Windows 文件系统大小写不敏感,所以**只改大小写**的重命名会被 Move-Item 当成同一个文件而静默跳过 —— 同一批里同时改了名字的那 16 个文件却成功了,于是"看起来能跑"。 最后用"先移到临时名、再移到目标名"的两步走解决,判断与替换全部改用显式大小写敏感的形式 (-creplace / -cmatch)。 顺带把名录指纹缓存从 MD5 换成 SHA256(PSAvoidUsingBrokenHashAlgorithms):它只是缓存键, 没有兼容负担。 验收:test.ps1 9/9 全绿(7 与 5.1)、100 个文件两版解析零错、276 个断言全过、 构建工具仍能合回单文件(3300 行)。
This commit is contained in:
1 parent
187d2759fd
commit
42f02d0eca
84 files changed
+1043
-1043
No files matched your search
+173
-173
@@ -54,7 +54,7 @@ BeforeAll {
|
||||
# 于是这里改成:把整条命令写进临时 .cmd,由 cmd 自己把输出重定向到文件,
|
||||
# 再用 Invoke-ExternalCommand 调 `cmd /c <批处理>`。退出码来自真实进程,
|
||||
# 输出从文件读回,全程不经过 PowerShell 的管道。
|
||||
function Invoke-BaknretScript {
|
||||
function Invoke-BakNRetScript {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Script,
|
||||
[hashtable]$Parameters = @{}
|
||||
@@ -337,29 +337,29 @@ Describe 'BackupList.txt 解析' {
|
||||
}
|
||||
|
||||
It '记号切分:引号内的空白不切分,引号本身留在记号里' {
|
||||
(@(Split-BaknretToken -Text 'A "B C" D') -join '|') | Should -Be 'A|"B C"|D'
|
||||
(@(Split-BakNRetToken -Text 'A "B C" D') -join '|') | Should -Be 'A|"B C"|D'
|
||||
# 引号不配对时按"引号延伸到行尾"处理,不抛异常
|
||||
(@(Split-BaknretToken -Text 'A "B C') -join '|') | Should -Be 'A|"B C'
|
||||
(@(Split-BaknretToken -Text '') -join '|') | Should -Be ''
|
||||
(@(Split-BakNRetToken -Text 'A "B C') -join '|') | Should -Be 'A|"B C'
|
||||
(@(Split-BakNRetToken -Text '') -join '|') | Should -Be ''
|
||||
}
|
||||
|
||||
It '记号识别只认完整记号' {
|
||||
(Test-BaknretMarker -Token '::') | Should -Be 'path'
|
||||
(Test-BaknretMarker -Token ':-') | Should -Be 'exclude'
|
||||
(Test-BaknretMarker -Token ':+') | Should -Be 'include'
|
||||
(Test-BaknretMarker -Token ':encrypt') | Should -Be 'encrypt'
|
||||
(Test-BaknretMarker -Token ':!encrypt') | Should -Be 'noencrypt'
|
||||
(Test-BaknretMarker -Token '@') | Should -Be 'at'
|
||||
(Test-BaknretMarker -Token 'C:\a:-b') | Should -BeNullOrEmpty
|
||||
(Test-BaknretMarker -Token ':') | Should -BeNullOrEmpty
|
||||
(Test-BakNRetMarker -Token '::') | Should -Be 'path'
|
||||
(Test-BakNRetMarker -Token ':-') | Should -Be 'exclude'
|
||||
(Test-BakNRetMarker -Token ':+') | Should -Be 'include'
|
||||
(Test-BakNRetMarker -Token ':encrypt') | Should -Be 'encrypt'
|
||||
(Test-BakNRetMarker -Token ':!encrypt') | Should -Be 'noencrypt'
|
||||
(Test-BakNRetMarker -Token '@') | Should -Be 'at'
|
||||
(Test-BakNRetMarker -Token 'C:\a:-b') | Should -BeNullOrEmpty
|
||||
(Test-BakNRetMarker -Token ':') | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It '去引号:成对才去,不成对原样返回' {
|
||||
(Remove-BaknretQuote -Text '"a"') | Should -Be 'a'
|
||||
(Remove-BaknretQuote -Text "'a'") | Should -Be 'a'
|
||||
(Remove-BaknretQuote -Text 'a') | Should -Be 'a'
|
||||
(Remove-BaknretQuote -Text '"a') | Should -Be '"a'
|
||||
(Remove-BaknretQuote -Text '""') | Should -Be ''
|
||||
(Remove-BakNRetQuote -Text '"a"') | Should -Be 'a'
|
||||
(Remove-BakNRetQuote -Text "'a'") | Should -Be 'a'
|
||||
(Remove-BakNRetQuote -Text 'a') | Should -Be 'a'
|
||||
(Remove-BakNRetQuote -Text '"a') | Should -Be '"a'
|
||||
(Remove-BakNRetQuote -Text '""') | Should -Be ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,49 +382,49 @@ Describe '归档命名' {
|
||||
|
||||
# 注意:Pester 的 It 名字里出现 $( ) 会被求值,所以标题里不要写子表达式。
|
||||
It '基础命名规则:末级名_from_上级路径用加号连接' {
|
||||
Get-BackupBaseName -RawPath 'C:\Programs\FooClolor' | Should -Be 'FooClolor_from_C_+Programs'
|
||||
Get-BakNRetBackupBaseName -RawPath 'C:\Programs\FooClolor' | Should -Be 'FooClolor_from_C_+Programs'
|
||||
}
|
||||
|
||||
It '/ 与 \ 以及重复分隔符结果一致' {
|
||||
$a = Get-BackupBaseName -RawPath 'C:\a/b'
|
||||
$b = Get-BackupBaseName -RawPath 'C:\\a\\\\b'
|
||||
$a = Get-BakNRetBackupBaseName -RawPath 'C:\a/b'
|
||||
$b = Get-BakNRetBackupBaseName -RawPath 'C:\\a\\\\b'
|
||||
$a | Should -Be $b
|
||||
}
|
||||
|
||||
It '命名与路径往返' {
|
||||
$base = Get-BackupBaseName -RawPath 'D:\UserData\Documents\Aria'
|
||||
Convert-BackupFileNameToPath -FileName ($base + '.7z') | Should -Be 'D:\UserData\Documents\Aria'
|
||||
$base = Get-BakNRetBackupBaseName -RawPath 'D:\UserData\Documents\Aria'
|
||||
Convert-BakNRetBackupFileNameToPath -FileName ($base + '.7z') | Should -Be 'D:\UserData\Documents\Aria'
|
||||
}
|
||||
|
||||
It '归档名里不含非法文件名字符' {
|
||||
$base = Get-BackupBaseName -RawPath 'C:\a<b>c|d?e*f'
|
||||
$base = Get-BakNRetBackupBaseName -RawPath 'C:\a<b>c|d?e*f'
|
||||
($base.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars())) | Should -Be -1
|
||||
}
|
||||
|
||||
It '%变量% 写法里的 % 会保留在归档名里' {
|
||||
Get-BackupBaseName -RawPath '%UserProfile%\.ssh' | Should -Be '.ssh_from_%UserProfile%'
|
||||
Get-BakNRetBackupBaseName -RawPath '%UserProfile%\.ssh' | Should -Be '.ssh_from_%UserProfile%'
|
||||
}
|
||||
|
||||
It '软件名条目:默认用软件名做归档名' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'my-app'
|
||||
(Get-ItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'my-app'
|
||||
(Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'my-app'
|
||||
}
|
||||
|
||||
It '字面路径条目:仍用路径命名算法(现有清单无需改写)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'C:\Programs\FooClolor'
|
||||
(Get-ItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'FooClolor_from_C_+Programs'
|
||||
(Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'FooClolor_from_C_+Programs'
|
||||
}
|
||||
|
||||
It '@pathname 用名录里的真实路径命名,而不是软件名' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'my-app @pathname'
|
||||
$expected = Get-BackupBaseName -RawPath $script:NamingApp
|
||||
(Get-ItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be $expected
|
||||
$expected = Get-BakNRetBackupBaseName -RawPath $script:NamingApp
|
||||
(Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be $expected
|
||||
}
|
||||
|
||||
It '名录里没有该名字:归档名退回可读目录名,并给出 Error' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'no-such-thing'
|
||||
(Get-ItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'no-such-thing'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3
|
||||
(Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'no-such-thing'
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3
|
||||
$resolved.Error | Should -Not -BeNullOrEmpty
|
||||
@($resolved.Items).Count | Should -Be 0
|
||||
}
|
||||
@@ -443,142 +443,142 @@ Describe '7z 排除参数翻译' {
|
||||
|
||||
function New-TestArchiveItem {
|
||||
param([string]$ArchivePath, [string]$RealPath = 'C:\nonexistent-path')
|
||||
New-BaknretArchiveItem -ArchivePath $ArchivePath -RealPath $RealPath -Kind 'path' -Origin 'path' -Exists $false -IsFile $false
|
||||
New-BakNRetArchiveItem -ArchivePath $ArchivePath -RealPath $RealPath -Kind 'path' -Origin 'path' -Exists $false -IsFile $false
|
||||
}
|
||||
}
|
||||
|
||||
It '相对模式自动补上归档根目录名' {
|
||||
$result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('logs\')
|
||||
$result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('logs\')
|
||||
@($result.Arguments) | Should -Contain '-x!Foo\logs'
|
||||
$result.Error | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It '模式已带根名前缀时,Split-BaknretPatternScope 先摘掉前缀,结果不重复' {
|
||||
It '模式已带根名前缀时,Split-BakNRetPatternScope 先摘掉前缀,结果不重复' {
|
||||
$item = New-TestArchiveItem -ArchivePath 'Foo'
|
||||
$map = Split-BaknretPatternScope -Items @($item) -Patterns @('Foo\logs\')
|
||||
$map = Split-BakNRetPatternScope -Items @($item) -Patterns @('Foo\logs\')
|
||||
(@($map[0]) -join '|') | Should -Be 'logs'
|
||||
|
||||
$result = Get-BaknretExcludeArgument -Item $item -Patterns @($map[0])
|
||||
$result = Get-BakNRetExcludeArgument -Item $item -Patterns @($map[0])
|
||||
@($result.Arguments) | Should -Contain '-x!Foo\logs'
|
||||
@($result.Arguments) | Should -Not -Contain '-x!Foo\Foo\logs'
|
||||
}
|
||||
|
||||
It '! 前缀翻译成递归组件匹配(-xr!)' {
|
||||
$result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('!*Cache')
|
||||
$result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('!*Cache')
|
||||
@($result.Arguments) | Should -Contain '-xr!*Cache'
|
||||
}
|
||||
|
||||
It '模式里的空格转成 ?(归档项自己的名字按原样保留)' {
|
||||
$result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('Default\Code Cache')
|
||||
$result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('Default\Code Cache')
|
||||
@($result.Arguments) | Should -Contain '-x!User Data\Default\Code?Cache'
|
||||
|
||||
$plain = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('Default\Code Cache')
|
||||
$plain = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('Default\Code Cache')
|
||||
@($plain.Arguments) | Should -Contain '-x!Foo\Default\Code?Cache'
|
||||
}
|
||||
|
||||
It '生成的参数里绝不出现引号(旧实现 -x!"路径" 让排除全部失效)' {
|
||||
$result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('!*Cache', 'Default\Code Cache')
|
||||
$result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('!*Cache', 'Default\Code Cache')
|
||||
(@($result.Arguments) -join ' ') | Should -Not -Match '"'
|
||||
}
|
||||
|
||||
It '空模式被忽略' {
|
||||
$result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('', ' ', '!')
|
||||
$result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('', ' ', '!')
|
||||
@($result.Arguments).Count | Should -Be 0
|
||||
$result.Error | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It '!re: 会把正则展开成精确的 -x! 参数' {
|
||||
$item = New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree
|
||||
$result = Get-BaknretExcludeArgument -Item $item -Patterns @('!re:^Cache$')
|
||||
$result = Get-BakNRetExcludeArgument -Item $item -Patterns @('!re:^Cache$')
|
||||
@($result.Arguments) | Should -Contain '-x!Top\sub\Cache'
|
||||
|
||||
# 命中目录后不再往下走(一个命中只产生一条参数,避免命令行爆炸)
|
||||
$pruned = Get-BaknretExcludeArgument -Item $item -Patterns @('!re:^sub$')
|
||||
$pruned = Get-BakNRetExcludeArgument -Item $item -Patterns @('!re:^sub$')
|
||||
@($pruned.Arguments) | Should -Contain '-x!Top\sub'
|
||||
@($pruned.Arguments).Count | Should -Be 1
|
||||
}
|
||||
|
||||
It '非法正则给出 Error,而不是抛异常' {
|
||||
$result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree) -Patterns @('!re:[')
|
||||
$result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree) -Patterns @('!re:[')
|
||||
@($result.Arguments).Count | Should -Be 0
|
||||
$result.Error | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It '正则命中数超过上限时明确报错' {
|
||||
$result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree) `
|
||||
$result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree) `
|
||||
-Patterns @('!re:.') -MaxRegexMatches 1
|
||||
$result.Error | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It '参数总长超过安全上限时明确报错' {
|
||||
$result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top') `
|
||||
$result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top') `
|
||||
-Patterns @('!*zzz') -MaxCommandLineChars 5
|
||||
$result.Error | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Split-BaknretPatternScope:指名 Slot 的模式只分给那个 Slot' {
|
||||
It 'Split-BakNRetPatternScope:指名 Slot 的模式只分给那个 Slot' {
|
||||
$items = @(
|
||||
New-TestArchiveItem -ArchivePath 'Alpha'
|
||||
New-TestArchiveItem -ArchivePath 'Beta'
|
||||
)
|
||||
$map = Split-BaknretPatternScope -Items $items -Patterns @('Alpha\logs\')
|
||||
$map = Split-BakNRetPatternScope -Items $items -Patterns @('Alpha\logs\')
|
||||
(@($map[0]) -join '|') | Should -Be 'logs'
|
||||
@($map[1]).Count | Should -Be 0
|
||||
|
||||
# 用 / 分隔也一样
|
||||
$slash = Split-BaknretPatternScope -Items $items -Patterns @('Beta/sub')
|
||||
$slash = Split-BakNRetPatternScope -Items $items -Patterns @('Beta/sub')
|
||||
(@($slash[1]) -join '|') | Should -Be 'sub'
|
||||
@($slash[0]).Count | Should -Be 0
|
||||
}
|
||||
|
||||
It 'Split-BaknretPatternScope:没点名任何 Slot 的模式广播给每一项' {
|
||||
It 'Split-BakNRetPatternScope:没点名任何 Slot 的模式广播给每一项' {
|
||||
$items = @(
|
||||
New-TestArchiveItem -ArchivePath 'Alpha'
|
||||
New-TestArchiveItem -ArchivePath 'Beta'
|
||||
)
|
||||
$map = Split-BaknretPatternScope -Items $items -Patterns @('plain\path', 'Alpha')
|
||||
$map = Split-BakNRetPatternScope -Items $items -Patterns @('plain\path', 'Alpha')
|
||||
(@($map[0]) -join '|') | Should -Be 'plain\path|Alpha'
|
||||
(@($map[1]) -join '|') | Should -Be 'plain\path|Alpha'
|
||||
}
|
||||
|
||||
It 'Split-BaknretPatternScope:! 与 !re: 模式广播给每一项' {
|
||||
It 'Split-BakNRetPatternScope:! 与 !re: 模式广播给每一项' {
|
||||
$items = @(
|
||||
New-TestArchiveItem -ArchivePath 'Alpha'
|
||||
New-TestArchiveItem -ArchivePath 'Beta'
|
||||
)
|
||||
$map = Split-BaknretPatternScope -Items $items -Patterns @('!*Cache', '!re:^x$')
|
||||
$map = Split-BakNRetPatternScope -Items $items -Patterns @('!*Cache', '!re:^x$')
|
||||
(@($map[0]) -join '|') | Should -Be '!*Cache|!re:^x$'
|
||||
(@($map[1]) -join '|') | Should -Be '!*Cache|!re:^x$'
|
||||
}
|
||||
|
||||
It 'Split-BaknretPatternScope:每个归档项都有键(没有模式时是空数组)' {
|
||||
It 'Split-BakNRetPatternScope:每个归档项都有键(没有模式时是空数组)' {
|
||||
$items = @(
|
||||
New-TestArchiveItem -ArchivePath 'Alpha'
|
||||
New-TestArchiveItem -ArchivePath 'Beta'
|
||||
)
|
||||
$map = Split-BaknretPatternScope -Items $items -Patterns @()
|
||||
$map = Split-BakNRetPatternScope -Items $items -Patterns @()
|
||||
$map.ContainsKey(0) | Should -BeTrue
|
||||
$map.ContainsKey(1) | Should -BeTrue
|
||||
@($map[0]).Count | Should -Be 0
|
||||
}
|
||||
|
||||
It 'Merge-BaknretExcludeArgument 去重且保持顺序' {
|
||||
$merged = Merge-BaknretExcludeArgument -ArgumentLists @(@('-x!a', '-x!b'), @('-x!b', '-x!c'))
|
||||
It 'Merge-BakNRetExcludeArgument 去重且保持顺序' {
|
||||
$merged = Merge-BakNRetExcludeArgument -ArgumentLists @(@('-x!a', '-x!b'), @('-x!b', '-x!c'))
|
||||
(@($merged) -join ' ') | Should -Be '-x!a -x!b -x!c'
|
||||
}
|
||||
|
||||
It '多 Slot 条目:逐项分配 + 翻译 + 去重合成一份参数' {
|
||||
$items = @(
|
||||
New-BaknretArchiveItem -ArchivePath 'Alpha' -RealPath $script:ExcludeTree -Kind 'slot' -Slot 'Alpha' -Origin 'catalog' -Exists $true
|
||||
New-BaknretArchiveItem -ArchivePath 'Beta' -RealPath $script:ExcludeTree -Kind 'slot' -Slot 'Beta' -Origin 'catalog' -Exists $true
|
||||
New-BakNRetArchiveItem -ArchivePath 'Alpha' -RealPath $script:ExcludeTree -Kind 'slot' -Slot 'Alpha' -Origin 'catalog' -Exists $true
|
||||
New-BakNRetArchiveItem -ArchivePath 'Beta' -RealPath $script:ExcludeTree -Kind 'slot' -Slot 'Beta' -Origin 'catalog' -Exists $true
|
||||
)
|
||||
$patterns = @('Alpha\logs\', '!*Cache', 'Beta\sub', '!*Cache')
|
||||
$map = Split-BaknretPatternScope -Items $items -Patterns $patterns
|
||||
$map = Split-BakNRetPatternScope -Items $items -Patterns $patterns
|
||||
$lists = @()
|
||||
for ($index = 0; $index -lt $items.Count; $index++) {
|
||||
$lists += , @((Get-BaknretExcludeArgument -Item $items[$index] -Patterns @($map[$index])).Arguments)
|
||||
$lists += , @((Get-BakNRetExcludeArgument -Item $items[$index] -Patterns @($map[$index])).Arguments)
|
||||
}
|
||||
$effective = @(Merge-BaknretExcludeArgument -ArgumentLists $lists)
|
||||
$effective = @(Merge-BakNRetExcludeArgument -ArgumentLists $lists)
|
||||
$effective | Should -Contain '-x!Alpha\logs'
|
||||
$effective | Should -Contain '-x!Beta\sub'
|
||||
$effective | Should -Contain '-xr!*Cache'
|
||||
@@ -600,8 +600,8 @@ Describe '归档项与暂存目录' {
|
||||
Set-Content -Encoding UTF8 -LiteralPath $script:StagingFile 'file-content'
|
||||
}
|
||||
|
||||
It 'New-BaknretArchiveItem 规范化归档内路径并算出 TopName' {
|
||||
$item = New-BaknretArchiveItem -ArchivePath '\Alpha\Sub\' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha'
|
||||
It 'New-BakNRetArchiveItem 规范化归档内路径并算出 TopName' {
|
||||
$item = New-BakNRetArchiveItem -ArchivePath '\Alpha\Sub\' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha'
|
||||
$item.ArchivePath | Should -Be 'Alpha\Sub'
|
||||
$item.TopName | Should -Be 'Alpha'
|
||||
$item.Kind | Should -Be 'slot'
|
||||
@@ -610,10 +610,10 @@ Describe '归档项与暂存目录' {
|
||||
@($item.Exclude).Count | Should -Be 0
|
||||
}
|
||||
|
||||
It 'Get-BaknretArchiveTopName 取归档内相对路径的第一段' {
|
||||
(Get-BaknretArchiveTopName -ArchivePath 'A\B\c.txt') | Should -Be 'A'
|
||||
(Get-BaknretArchiveTopName -ArchivePath ' A ') | Should -Be 'A'
|
||||
(Get-BaknretArchiveTopName -ArchivePath '') | Should -Be ''
|
||||
It 'Get-BakNRetArchiveTopName 取归档内相对路径的第一段' {
|
||||
(Get-BakNRetArchiveTopName -ArchivePath 'A\B\c.txt') | Should -Be 'A'
|
||||
(Get-BakNRetArchiveTopName -ArchivePath ' A ') | Should -Be 'A'
|
||||
(Get-BakNRetArchiveTopName -ArchivePath '') | Should -Be ''
|
||||
}
|
||||
|
||||
It '暂存半途失败会自己清干净(不留指向真实数据的 junction)' {
|
||||
@@ -628,12 +628,12 @@ Describe '归档项与暂存目录' {
|
||||
$items = @(
|
||||
# 第一项会成功挂上,而且是**目录走 junction** —— 这正是危险的那个物件:它指向
|
||||
# 真实数据,而临时目录迟早会被某次 Remove-Item -Recurse 扫到。
|
||||
New-BaknretArchiveItem -ArchivePath 'Good' -RealPath $realDir -Kind 'slot' -Slot 'Good' -Exists $true -IsFile $false
|
||||
New-BakNRetArchiveItem -ArchivePath 'Good' -RealPath $realDir -Kind 'slot' -Slot 'Good' -Exists $true -IsFile $false
|
||||
# 第二项必然失败:源文件不存在,硬链接与复制都会失败
|
||||
New-BaknretArchiveItem -ArchivePath 'Missing.txt' -RealPath (Join-Path $base 'does-not-exist.txt') -Kind 'slot' -Slot 'Missing' -Exists $false -IsFile $true
|
||||
New-BakNRetArchiveItem -ArchivePath 'Missing.txt' -RealPath (Join-Path $base 'does-not-exist.txt') -Kind 'slot' -Slot 'Missing' -Exists $false -IsFile $true
|
||||
)
|
||||
|
||||
{ New-BaknretArchiveStaging -Items $items -Root (Join-Path $sandbox 'stage') } | Should -Throw
|
||||
{ New-BakNRetArchiveStaging -Items $items -Root (Join-Path $sandbox 'stage') } | Should -Throw
|
||||
|
||||
$left = @(Get-ChildItem -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue)
|
||||
@($left | Where-Object { $_.LinkType -eq 'Junction' }).Count | Should -Be 0
|
||||
@@ -644,12 +644,12 @@ Describe '归档项与暂存目录' {
|
||||
|
||||
It '目录项用 junction 挂进暂存目录,文件项用硬链接(名字就是归档内路径)' {
|
||||
$items = @(
|
||||
New-BaknretArchiveItem -ArchivePath 'Alpha' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' -Exists $true -IsFile $false
|
||||
New-BaknretArchiveItem -ArchivePath 'Nested\Deep' -RealPath $script:StagingDir -Kind 'include' -Origin 'include' -Exists $true -IsFile $false
|
||||
New-BaknretArchiveItem -ArchivePath 'Cfg' -RealPath $script:StagingFile -Kind 'slot' -Slot 'Cfg' -Exists $true -IsFile $true
|
||||
New-BakNRetArchiveItem -ArchivePath 'Alpha' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' -Exists $true -IsFile $false
|
||||
New-BakNRetArchiveItem -ArchivePath 'Nested\Deep' -RealPath $script:StagingDir -Kind 'include' -Origin 'include' -Exists $true -IsFile $false
|
||||
New-BakNRetArchiveItem -ArchivePath 'Cfg' -RealPath $script:StagingFile -Kind 'slot' -Slot 'Cfg' -Exists $true -IsFile $true
|
||||
)
|
||||
|
||||
$stage = New-BaknretArchiveStaging -Items $items
|
||||
$stage = New-BakNRetArchiveStaging -Items $items
|
||||
try {
|
||||
$alpha = Get-Item -LiteralPath (Join-Path $stage 'Alpha') -Force
|
||||
$alpha.LinkType | Should -Be 'Junction'
|
||||
@@ -663,15 +663,15 @@ Describe '归档项与暂存目录' {
|
||||
(Get-Content -Encoding UTF8 -LiteralPath $cfg.FullName -Raw).Trim() | Should -Be 'file-content'
|
||||
}
|
||||
finally {
|
||||
Remove-BaknretArchiveStaging -Root $stage
|
||||
Remove-BakNRetArchiveStaging -Root $stage
|
||||
}
|
||||
}
|
||||
|
||||
It 'Remove-BaknretArchiveStaging 只删连接点,不顺着走进真实目录' {
|
||||
$stage = New-BaknretArchiveStaging -Items @(
|
||||
New-BaknretArchiveItem -ArchivePath 'Alpha' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' -Exists $true -IsFile $false
|
||||
It 'Remove-BakNRetArchiveStaging 只删连接点,不顺着走进真实目录' {
|
||||
$stage = New-BakNRetArchiveStaging -Items @(
|
||||
New-BakNRetArchiveItem -ArchivePath 'Alpha' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' -Exists $true -IsFile $false
|
||||
)
|
||||
Remove-BaknretArchiveStaging -Root $stage
|
||||
Remove-BakNRetArchiveStaging -Root $stage
|
||||
|
||||
Test-Path -LiteralPath $stage | Should -BeFalse
|
||||
Test-Path -LiteralPath (Join-Path $script:StagingDir 'inner\a.txt') | Should -BeTrue
|
||||
@@ -683,23 +683,23 @@ Describe '命令行参数拼接' {
|
||||
# ============================================================================
|
||||
|
||||
It '无空格参数原样输出' {
|
||||
ConvertTo-NativeArgumentString -ArgumentList @('a', '-mx=9') | Should -Be 'a -mx=9'
|
||||
ConvertTo-BakNRetNativeArgumentString -ArgumentList @('a', '-mx=9') | Should -Be 'a -mx=9'
|
||||
}
|
||||
|
||||
It '含空格参数加引号' {
|
||||
ConvertTo-NativeArgumentString -ArgumentList @('C:\a b\c') | Should -Be '"C:\a b\c"'
|
||||
ConvertTo-BakNRetNativeArgumentString -ArgumentList @('C:\a b\c') | Should -Be '"C:\a b\c"'
|
||||
}
|
||||
|
||||
It '引号内的结尾反斜杠翻倍(否则会被当成转义引号)' {
|
||||
ConvertTo-NativeArgumentString -ArgumentList @('C:\a b\') | Should -Be '"C:\a b\\"'
|
||||
ConvertTo-BakNRetNativeArgumentString -ArgumentList @('C:\a b\') | Should -Be '"C:\a b\\"'
|
||||
}
|
||||
|
||||
It '内部引号被转义' {
|
||||
ConvertTo-NativeArgumentString -ArgumentList @('a"b c') | Should -Be '"a\"b c"'
|
||||
ConvertTo-BakNRetNativeArgumentString -ArgumentList @('a"b c') | Should -Be '"a\"b c"'
|
||||
}
|
||||
|
||||
It '空参数输出一对空引号' {
|
||||
ConvertTo-NativeArgumentString -ArgumentList @('') | Should -Be '""'
|
||||
ConvertTo-BakNRetNativeArgumentString -ArgumentList @('') | Should -Be '""'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,11 +709,11 @@ Describe 'manifest 与配置' {
|
||||
|
||||
It 'manifest 读写往返' {
|
||||
$path = Join-Path $script:Sandbox 'roundtrip\manifest.json'
|
||||
$manifest = Read-BaknretManifest -Path $path
|
||||
$manifest = Read-BakNRetManifest -Path $path
|
||||
$manifest.items['demo'] = [pscustomobject]@{ archive = 'demo.7z'; action = 'backed-up'; verified = $true }
|
||||
Write-BaknretManifest -Path $path -Manifest $manifest | Out-Null
|
||||
Write-BakNRetManifest -Path $path -Manifest $manifest | Out-Null
|
||||
|
||||
$again = Read-BaknretManifest -Path $path
|
||||
$again = Read-BakNRetManifest -Path $path
|
||||
$again.items.Count | Should -Be 1
|
||||
$again.items['demo'].archive | Should -Be 'demo.7z'
|
||||
$again.items['demo'].verified | Should -BeTrue
|
||||
@@ -721,7 +721,7 @@ Describe 'manifest 与配置' {
|
||||
|
||||
It 'manifest 原样保存 layouts(name/kind),全新恢复时靠它判断目录还是文件' {
|
||||
$path = Join-Path $script:Sandbox 'layouts\manifest.json'
|
||||
$manifest = Read-BaknretManifest -Path $path
|
||||
$manifest = Read-BakNRetManifest -Path $path
|
||||
$manifest.items['app'] = [pscustomobject]@{
|
||||
archive = 'app.7z'
|
||||
layouts = @(
|
||||
@@ -729,28 +729,28 @@ Describe 'manifest 与配置' {
|
||||
[pscustomobject]@{ name = 'Cfg'; kind = 'file' }
|
||||
)
|
||||
}
|
||||
Write-BaknretManifest -Path $path -Manifest $manifest | Out-Null
|
||||
Write-BakNRetManifest -Path $path -Manifest $manifest | Out-Null
|
||||
|
||||
$again = Read-BaknretManifest -Path $path
|
||||
$again = Read-BakNRetManifest -Path $path
|
||||
@($again.items['app'].layouts).Count | Should -Be 2
|
||||
(@($again.items['app'].layouts | ForEach-Object { $_.name + ':' + $_.kind }) -join '|') | Should -Be 'Data:dir|Cfg:file'
|
||||
}
|
||||
|
||||
It 'manifest 损坏时不抛异常,而是重建空清单' {
|
||||
$path = Write-ListFile -Path (Join-Path $script:Sandbox 'broken.json') -Content '{ this is not json'
|
||||
{ $script:broken = Read-BaknretManifest -Path $path } | Should -Not -Throw
|
||||
{ $script:broken = Read-BakNRetManifest -Path $path } | Should -Not -Throw
|
||||
$script:broken.items.Count | Should -Be 0
|
||||
}
|
||||
|
||||
It '配置缺失时返回默认值' {
|
||||
$config = Get-BaknretConfig -Path (Join-Path $script:Sandbox 'no-such-config.psd1')
|
||||
$config = Get-BakNRetConfig -Path (Join-Path $script:Sandbox 'no-such-config.psd1')
|
||||
$config.BackupDir | Should -Be 'Backups'
|
||||
$config.VerifyArchive | Should -BeTrue
|
||||
}
|
||||
|
||||
It '配置嵌套段落合并且不丢默认键' {
|
||||
$path = Write-ListFile -Path (Join-Path $script:Sandbox 'cfg.psd1') -Content "@{ MinFreeSpaceGB = 3; Encryption = @{ Enabled = `$true } }"
|
||||
$config = Get-BaknretConfig -Path $path
|
||||
$config = Get-BakNRetConfig -Path $path
|
||||
$config.MinFreeSpaceGB | Should -Be 3
|
||||
$config.Encryption.Enabled | Should -BeTrue
|
||||
# 没写到的子键要保留默认值。Encryption 是哈希表,用 ContainsKey 查。
|
||||
@@ -761,9 +761,9 @@ Describe 'manifest 与配置' {
|
||||
$saved = $env:BAKNRET_PASSWORD
|
||||
try {
|
||||
$env:BAKNRET_PASSWORD = 'sekrit'
|
||||
(Get-BaknretPassword) | Should -Be 'sekrit'
|
||||
(Get-BakNRetPassword) | Should -Be 'sekrit'
|
||||
$env:BAKNRET_PASSWORD = $null
|
||||
(Get-BaknretPassword) | Should -BeNullOrEmpty
|
||||
(Get-BakNRetPassword) | Should -BeNullOrEmpty
|
||||
}
|
||||
finally {
|
||||
$env:BAKNRET_PASSWORD = $saved
|
||||
@@ -827,7 +827,7 @@ Describe '软件名录' {
|
||||
}
|
||||
|
||||
It '名录解析:条目与 Slot 的字段集合就是新契约' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$entry = $catalog['single']
|
||||
(@($entry.PSObject.Properties.Name) -join ',') | Should -Be 'Name,Path,Description,Slots,Kind,Missing,Error,Raw'
|
||||
$entry.Name | Should -Be 'single'
|
||||
@@ -849,7 +849,7 @@ Describe '软件名录' {
|
||||
}
|
||||
|
||||
It '一个软件多个 Slot:Kind=Multi,Slot 按名字排序' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog['multi'].Kind | Should -Be 'Multi'
|
||||
@($catalog['multi'].Slots).Count | Should -Be 2
|
||||
(@($catalog['multi'].Slots | ForEach-Object { $_.Name }) -join ',') | Should -Be 'Alpha,Zeta'
|
||||
@@ -858,7 +858,7 @@ Describe '软件名录' {
|
||||
}
|
||||
|
||||
It 'Kind:Single / Multi / Partial / Unresolved / Invalid' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog['single'].Kind | Should -Be 'Single'
|
||||
$catalog['multi'].Kind | Should -Be 'Multi'
|
||||
$catalog['partial'].Kind | Should -Be 'Partial'
|
||||
@@ -873,7 +873,7 @@ Describe '软件名录' {
|
||||
}
|
||||
|
||||
It '前缀补全:<名>_<后缀> 与 <名>-<后缀> 都会被补全' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog['legendary'].Slots[0].Resolved | Should -Be (Join-Path $script:CatRoot 'legendary_2.0.4')
|
||||
$catalog['legendary'].Slots[0].Suffixed | Should -BeTrue
|
||||
|
||||
@@ -882,12 +882,12 @@ Describe '软件名录' {
|
||||
}
|
||||
|
||||
It '不会把 Legendary 误配成 LegendarySomething' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog['legendary'].Slots[0].Resolved | Should -Not -Be (Join-Path $script:CatRoot 'LegendarySomething')
|
||||
}
|
||||
|
||||
It '一个 Slot 命中多个候选目录:报 Error,绝不悄悄挑一棵树' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$entry = $catalog['dupslot']
|
||||
$entry.Error | Should -Match '只能对应一个目录'
|
||||
$entry.Error | Should -Match 'dup_1'
|
||||
@@ -897,14 +897,14 @@ Describe '软件名录' {
|
||||
}
|
||||
|
||||
It '文件 Slot:Path 指向文件时 IsFile 为真,Encrypt 也带上' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$slot = $catalog['fileslot'].Slots[0]
|
||||
$slot.IsFile | Should -BeTrue
|
||||
$slot.Encrypt | Should -BeTrue
|
||||
}
|
||||
|
||||
It '%变量% 会在名录路径里展开' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$catalog['envpath'].Slots[0].Resolved | Should -Be ([Environment]::ExpandEnvironmentVariables('%TEMP%'))
|
||||
}
|
||||
|
||||
@@ -916,39 +916,39 @@ Describe '软件名录' {
|
||||
}
|
||||
|
||||
It '读取结果按"路径 + 时间戳 + 长度 + 内容 MD5"缓存' {
|
||||
$first = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3
|
||||
$second = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3
|
||||
$first = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3
|
||||
$second = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3
|
||||
[object]::ReferenceEquals($first, $second) | Should -BeTrue
|
||||
|
||||
$fresh = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
$fresh = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache
|
||||
[object]::ReferenceEquals($first, $fresh) | Should -BeFalse
|
||||
}
|
||||
|
||||
It '名录文件内容变了以后缓存自动失效' {
|
||||
$path = Write-ListFile -Path (Join-Path $script:CatRoot 'cache.psd1') -Content "@{ 'x' = @{ S = @{ Path = '$script:CatReal'; Description = 'one' } } }"
|
||||
(Get-SoftwareCatalog -Path $path -MaxDepth 3)['x'].Description | Should -Be 'one'
|
||||
(Get-BakNRetSoftwareCatalog -Path $path -MaxDepth 3)['x'].Description | Should -Be 'one'
|
||||
|
||||
Write-ListFile -Path $path -Content "@{ 'x' = @{ S = @{ Path = '$script:CatReal'; Description = 'two' } } }" | Out-Null
|
||||
(Get-SoftwareCatalog -Path $path -MaxDepth 3)['x'].Description | Should -Be 'two'
|
||||
(Get-BakNRetSoftwareCatalog -Path $path -MaxDepth 3)['x'].Description | Should -Be 'two'
|
||||
}
|
||||
|
||||
It 'Includes:分文件维护的名录会被合并' {
|
||||
Write-ListFile -Path (Join-Path $script:CatRoot 'Extra.psd1') -Content "@{ 'extra-app' = @{ S = @{ Path = '$script:CatReal' } } }" | Out-Null
|
||||
$main = Write-ListFile -Path (Join-Path $script:CatRoot 'Main.psd1') -Content "@{ Includes = @('Extra.psd1'); 'main-app' = @{ S = @{ Path = '$script:CatReal' } } }"
|
||||
$catalog = Get-SoftwareCatalog -Path $main -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $main -MaxDepth 3 -NoCache
|
||||
$catalog.ContainsKey('main-app') | Should -BeTrue
|
||||
$catalog.ContainsKey('extra-app') | Should -BeTrue
|
||||
}
|
||||
|
||||
It '旧的裸字符串 / 字符串数组 / 对象数组写法都会报 ERROR 并被跳过' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:LegacyFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:LegacyFile -MaxDepth 3 -NoCache
|
||||
$catalog.ContainsKey('bare') | Should -BeFalse
|
||||
$catalog.ContainsKey('arr') | Should -BeFalse
|
||||
$catalog.ContainsKey('objarr') | Should -BeFalse
|
||||
}
|
||||
|
||||
It '旧的 @{ Dirs = @(...) } 写法不再展开:留下 Invalid 条目和原因' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:LegacyFile -MaxDepth 3 -NoCache
|
||||
$catalog = Get-BakNRetSoftwareCatalog -Path $script:LegacyFile -MaxDepth 3 -NoCache
|
||||
$catalog.ContainsKey('dirstyle') | Should -BeTrue
|
||||
$catalog['dirstyle'].Kind | Should -Be 'Invalid'
|
||||
$catalog['dirstyle'].Error | Should -Not -BeNullOrEmpty
|
||||
@@ -956,14 +956,14 @@ Describe '软件名录' {
|
||||
}
|
||||
|
||||
It '软件名会被规范化成合法文件名' {
|
||||
$clean = Format-CatalogName -Name 'a<b>c:d/e'
|
||||
$clean = Format-BakNRetCatalogName -Name 'a<b>c:d/e'
|
||||
($clean.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars())) | Should -Be -1
|
||||
(Format-CatalogName -Name ' My App ') | Should -Be 'My App'
|
||||
(Format-BakNRetCatalogName -Name ' My App ') | Should -Be 'My App'
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
Describe 'Resolve-BackupEntry:条目解析' {
|
||||
Describe 'Resolve-BakNRetBackupEntry:条目解析' {
|
||||
# ============================================================================
|
||||
|
||||
BeforeAll {
|
||||
@@ -976,7 +976,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
|
||||
It 'resolve 与归档项的字段集合就是新契约(旧字段已删除)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'single'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
(@($resolved.PSObject.Properties.Name) -join ',') | Should -Be 'IsName,CatalogEntry,BaseName,ArchiveFlavor,Direction,Items,Encrypt,ExcludePatterns,HasExcludeOverride,Includes,HasIncludeOverride,Source,Error,Blocking'
|
||||
@($resolved.Items)[0].PSObject.Properties.Name | Should -Contain 'ArchivePath'
|
||||
@($resolved.Items)[0].PSObject.Properties.Name | Should -Contain 'Origin'
|
||||
@@ -985,7 +985,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
|
||||
It '软件名条目:isName/CatalogEntry/BaseName/ArchiveFlavor/Source' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'single'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved.IsName | Should -BeTrue
|
||||
$resolved.BaseName | Should -Be 'single'
|
||||
$resolved.ArchiveFlavor | Should -Be 'name'
|
||||
@@ -997,7 +997,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
|
||||
It '软件名条目:Items 来自 Slot(ArchivePath=Slot 名,Kind=slot,Origin=catalog)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'multi'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
@($resolved.Items).Count | Should -Be 2
|
||||
(@($resolved.Items | ForEach-Object { $_.ArchivePath }) -join ',') | Should -Be 'Alpha,Zeta'
|
||||
(@($resolved.Items | ForEach-Object { $_.Kind }) -join ',') | Should -Be 'slot,slot'
|
||||
@@ -1009,7 +1009,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
|
||||
It '字面路径条目:一个 path 项(ArchivePath 是末级名)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'C:\Programs\FooClolor'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved.IsName | Should -BeFalse
|
||||
$resolved.ArchiveFlavor | Should -Be 'path'
|
||||
@($resolved.Items).Count | Should -Be 1
|
||||
@@ -1021,7 +1021,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
|
||||
It '字面路径条目上的 @ Path= 覆盖目标路径,但归档名仍按原路径' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "C:\Programs\Foo @ Path='D:\Elsewhere'"
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved.Items[0].RealPath | Should -Be 'D:\Elsewhere'
|
||||
$resolved.BaseName | Should -Be 'Foo_from_C_+Programs'
|
||||
}
|
||||
@@ -1029,7 +1029,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
It '名录里的路径不存在时仍给出 Items(恢复要靠它还原回原位)' {
|
||||
$missingCatalog = Write-ListFile -Path (Join-Path $script:Sandbox 'catalog\Missing.psd1') -Content "@{ 'gone' = @{ S = @{ Path = 'C:\definitely-not-here-98765' } } }"
|
||||
$entry = ConvertFrom-BackupListLine -Line 'gone'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $missingCatalog -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $missingCatalog -MaxDepth 3
|
||||
@($resolved.Items).Count | Should -Be 1
|
||||
$resolved.Items[0].Exists | Should -BeFalse
|
||||
$resolved.Items[0].RealPath | Should -Be 'C:\definitely-not-here-98765'
|
||||
@@ -1037,7 +1037,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
|
||||
It ':: 覆盖 Path:单 Slot 条目直接生效' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "single :: $script:CatOther"
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
@($resolved.Items).Count | Should -Be 1
|
||||
$resolved.Items[0].ArchivePath | Should -Be 'Main'
|
||||
$resolved.Items[0].RealPath | Should -Be $script:CatOther
|
||||
@@ -1047,7 +1047,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
It ':: / @ Path= 覆盖遇到多 Slot 条目 -> Blocking(不猜是哪一个)' {
|
||||
foreach ($line in @("multi :: $script:CatOther", "multi @ Path='$script:CatOther'")) {
|
||||
$entry = ConvertFrom-BackupListLine -Line $line
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
@($resolved.Items).Count | Should -Be 0
|
||||
$resolved.Blocking | Should -Match '不能用一个'
|
||||
}
|
||||
@@ -1055,14 +1055,14 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
|
||||
It '条目级 :- 覆盖名录里的排除:HasExcludeOverride 为真' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'single :- logs\,!*Cache'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved.HasExcludeOverride | Should -BeTrue
|
||||
(@($resolved.ExcludePatterns) -join '|') | Should -Be 'logs\|!*Cache'
|
||||
}
|
||||
|
||||
It '名录 Slot 的 Include 会追加成 include 项' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'incapp'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
@($resolved.Items).Count | Should -Be 2
|
||||
$include = @($resolved.Items | Where-Object { $_.Origin -eq 'include' })
|
||||
$include.Count | Should -Be 1
|
||||
@@ -1074,7 +1074,7 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
It '条目级 :+ / @ Include= 覆盖名录里的 Include' {
|
||||
foreach ($line in @("incapp :+ Other:$script:CatOther", "incapp @ Include='Other:$script:CatOther'")) {
|
||||
$entry = ConvertFrom-BackupListLine -Line $line
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved.HasIncludeOverride | Should -BeTrue
|
||||
(@($resolved.Items | ForEach-Object { $_.ArchivePath }) -join ',') | Should -Be 'A,Other'
|
||||
@($resolved.Items)[1].RealPath | Should -Be $script:CatOther
|
||||
@@ -1082,34 +1082,34 @@ Describe 'Resolve-BackupEntry:条目解析' {
|
||||
}
|
||||
|
||||
It '加密:名录里各 Slot 取或;条目级 :encrypt / :!encrypt 覆盖' {
|
||||
(Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeFalse
|
||||
(Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'encapp') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue
|
||||
(Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'encapp :!encrypt') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeFalse
|
||||
(Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single :encrypt') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue
|
||||
(Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line "single @ Encrypt='true'") -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue
|
||||
(Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeFalse
|
||||
(Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'encapp') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue
|
||||
(Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'encapp :!encrypt') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeFalse
|
||||
(Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single :encrypt') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue
|
||||
(Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line "single @ Encrypt='true'") -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue
|
||||
}
|
||||
|
||||
It '方向标记会传递到 Resolve-BackupEntry.Direction' {
|
||||
(Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line '+ single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'backup'
|
||||
(Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line '- single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'restore'
|
||||
(Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'both'
|
||||
It '方向标记会传递到 Resolve-BakNRetBackupEntry.Direction' {
|
||||
(Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line '+ single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'backup'
|
||||
(Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line '- single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'restore'
|
||||
(Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'both'
|
||||
}
|
||||
|
||||
It '归档内路径冲突(include 与 Slot 同名)-> Blocking' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "conflict :+ Data:$script:CatOther"
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved.Blocking | Should -Match '归档内路径冲突'
|
||||
}
|
||||
|
||||
It '归档内路径冲突(父子关系)-> Blocking' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "conflict :+ Data\sub:$script:CatOther"
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved.Blocking | Should -Match '父子关系'
|
||||
}
|
||||
|
||||
It '名录里没有该名字:Error 给出,Items 为空' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'no-such-thing'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3
|
||||
$resolved.Error | Should -Match 'no-such-thing'
|
||||
@($resolved.Items).Count | Should -Be 0
|
||||
}
|
||||
@@ -1125,13 +1125,13 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
|
||||
$sentinel = 'SENTINEL-PW-9f3a'
|
||||
$logPath = $null
|
||||
try {
|
||||
$logPath = Start-BaknretLog -Directory $sandbox -Prefix 'secret'
|
||||
Set-BaknretDebug
|
||||
$logPath = Start-BakNRetLog -Directory $sandbox -Prefix 'secret'
|
||||
Set-BakNRetDebug
|
||||
$null = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', 'exit 0', "-p$sentinel")
|
||||
}
|
||||
finally {
|
||||
Set-BaknretDebug -Enabled:$false
|
||||
Stop-BaknretLog
|
||||
Set-BakNRetDebug -Enabled:$false
|
||||
Stop-BakNRetLog
|
||||
}
|
||||
|
||||
$logText = Get-Content -Encoding UTF8 -LiteralPath $logPath -Raw
|
||||
@@ -1150,21 +1150,21 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
|
||||
$first = $null
|
||||
$second = $null
|
||||
try {
|
||||
$first = Enter-BaknretRunLock -Directory $dir
|
||||
$first = Enter-BakNRetRunLock -Directory $dir
|
||||
$first | Should -Not -BeNullOrEmpty
|
||||
|
||||
$second = Enter-BaknretRunLock -Directory $dir
|
||||
$second = Enter-BakNRetRunLock -Directory $dir
|
||||
$second | Should -BeNullOrEmpty
|
||||
|
||||
Test-Path -LiteralPath (Get-BaknretRunLockPath -Directory $dir) | Should -BeTrue
|
||||
Test-Path -LiteralPath (Get-BakNRetRunLockPath -Directory $dir) | Should -BeTrue
|
||||
}
|
||||
finally {
|
||||
Exit-BaknretRunLock -Lock $second
|
||||
Exit-BaknretRunLock -Lock $first
|
||||
Exit-BakNRetRunLock -Lock $second
|
||||
Exit-BakNRetRunLock -Lock $first
|
||||
}
|
||||
|
||||
# 锁文件是独占打开的(FileShare.None),内容只能在释放之后读
|
||||
(Get-Content -Encoding UTF8 -LiteralPath (Get-BaknretRunLockPath -Directory $dir) -Raw) |
|
||||
(Get-Content -Encoding UTF8 -LiteralPath (Get-BakNRetRunLockPath -Directory $dir) -Raw) |
|
||||
Should -Match "pid=$PID"
|
||||
|
||||
Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
@@ -1173,13 +1173,13 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
|
||||
It '运行锁:释放之后可以重新取得' {
|
||||
$dir = Join-Path $env:TEMP ("bnr-lock-" + [guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
try {
|
||||
$first = Enter-BaknretRunLock -Directory $dir
|
||||
$first = Enter-BakNRetRunLock -Directory $dir
|
||||
$first | Should -Not -BeNullOrEmpty
|
||||
Exit-BaknretRunLock -Lock $first
|
||||
Exit-BakNRetRunLock -Lock $first
|
||||
|
||||
$second = Enter-BaknretRunLock -Directory $dir
|
||||
$second = Enter-BakNRetRunLock -Directory $dir
|
||||
$second | Should -Not -BeNullOrEmpty
|
||||
Exit-BaknretRunLock -Lock $second
|
||||
Exit-BakNRetRunLock -Lock $second
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
@@ -1192,7 +1192,7 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
|
||||
$empty = Join-Path $env:TEMP ("bnr-empty-" + [guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
New-Item -ItemType Directory -Path $empty -Force | Out-Null
|
||||
try {
|
||||
$summary = Get-FolderSummary -FolderPath $empty
|
||||
$summary = Get-BakNRetFolderSummary -FolderPath $empty
|
||||
$summary.FileCount | Should -Be 0
|
||||
$summary.TotalSize | Should -Not -BeNullOrEmpty
|
||||
$summary.TotalSize | Should -Be 0
|
||||
@@ -1207,18 +1207,18 @@ Describe '外部命令退出码(旧实现的核心缺陷)' {
|
||||
New-Item -ItemType Directory -Path $base -Force | Out-Null
|
||||
$target = Join-Path $base 'text.json'
|
||||
try {
|
||||
Write-BaknretAtomicText -Path $target -Text 'FIRST' | Out-Null
|
||||
Write-BakNRetAtomicText -Path $target -Text 'FIRST' | Out-Null
|
||||
(Get-Content -Encoding UTF8 -LiteralPath $target -Raw) | Should -Be 'FIRST'
|
||||
|
||||
# 目标已存在时走 File.Replace
|
||||
Write-BaknretAtomicText -Path $target -Text 'SECOND' | Out-Null
|
||||
Write-BakNRetAtomicText -Path $target -Text 'SECOND' | Out-Null
|
||||
(Get-Content -Encoding UTF8 -LiteralPath $target -Raw) | Should -Be 'SECOND'
|
||||
"$target.tmp" | Should -Not -Exist
|
||||
|
||||
# 目标只读 -> 替换必然失败 -> 旧内容必须还在。这正是 File.Replace 与"先删后移"
|
||||
# 的区别:后者失败时旧文件已经没了,而 manifest 丢了只能靠文件名反推。
|
||||
(Get-Item -LiteralPath $target).IsReadOnly = $true
|
||||
{ Write-BaknretAtomicText -Path $target -Text 'SHOULD-NOT-LAND' } | Should -Throw
|
||||
{ Write-BakNRetAtomicText -Path $target -Text 'SHOULD-NOT-LAND' } | Should -Throw
|
||||
(Get-Content -Encoding UTF8 -LiteralPath $target -Raw) | Should -Be 'SECOND'
|
||||
}
|
||||
finally {
|
||||
@@ -1260,9 +1260,9 @@ Describe '集成:真实 7z 压缩与排除规则' -Skip:(-not $script:HasSeven
|
||||
|
||||
It '排除规则与空格处理在真实归档上生效' {
|
||||
$patterns = @('!*Cache', 'component_crx_cache', 'Default\Code Cache', 'Default\Extensions', 'Default\IndexedDB')
|
||||
$item = New-BaknretArchiveItem -ArchivePath $script:IntegrationItem -RealPath $script:IntegrationPath `
|
||||
$item = New-BakNRetArchiveItem -ArchivePath $script:IntegrationItem -RealPath $script:IntegrationPath `
|
||||
-Kind 'path' -Origin 'path' -Exists $true -IsFile $false
|
||||
$excludeArguments = @((Get-BaknretExcludeArgument -Item $item -Patterns $patterns).Arguments)
|
||||
$excludeArguments = @((Get-BakNRetExcludeArgument -Item $item -Patterns $patterns).Arguments)
|
||||
|
||||
$archive = Join-Path $script:IntegrationRoot 'excl.7z'
|
||||
$arguments = @('a', '-t7z', '-mx=1', '-bso0', '-bsp0') + $excludeArguments + @($archive, $script:IntegrationItem)
|
||||
@@ -1316,14 +1316,14 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
$script:E2EHashes = New-VerifiableSourceTree -Root $script:E2ESource
|
||||
Write-ListFile -Path $script:E2EList -Content "# e2e`n$script:E2ESource :- logs\,!*Cache`n" | Out-Null
|
||||
|
||||
$script:BackupRun = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
$script:BackupRun = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $script:E2EList
|
||||
BackupDir = $script:E2EBackupDir
|
||||
Force = $true
|
||||
QuietTool = $true
|
||||
}
|
||||
$script:E2EManifestPath = Join-Path $script:E2EBackupDir 'manifest.json'
|
||||
$script:E2EManifest = Read-BaknretManifest -Path $script:E2EManifestPath
|
||||
$script:E2EManifest = Read-BakNRetManifest -Path $script:E2EManifestPath
|
||||
$script:E2EArchive = @(Get-ChildItem -LiteralPath $script:E2EBackupDir -File -Filter *.7z)[0]
|
||||
}
|
||||
|
||||
@@ -1352,7 +1352,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
}
|
||||
|
||||
It '字面路径条目沿用 <末级名> 布局;归档里保留应保留内容、不含被排除项' {
|
||||
(Get-BaknretArchiveTopName -ArchivePath 'My Code Space\keep.txt') | Should -Be 'My Code Space'
|
||||
(Get-BakNRetArchiveTopName -ArchivePath 'My Code Space\keep.txt') | Should -Be 'My Code Space'
|
||||
|
||||
$verify = Join-Path $script:E2ERoot 'verify'
|
||||
New-Item -ItemType Directory -Path $verify -Force | Out-Null
|
||||
@@ -1370,7 +1370,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
$record.roots | Should -Contain 'My Code Space'
|
||||
|
||||
# 和归档里的真实内容对一次账,而不是只信 manifest 自己
|
||||
$topLevel = @(Get-ArchiveTopLevelNames -ArchivePath $script:E2EArchive.FullName -SevenZip $script:SevenZip)
|
||||
$topLevel = @(Get-BakNRetArchiveTopLevelNames -ArchivePath $script:E2EArchive.FullName -SevenZip $script:SevenZip)
|
||||
(@($topLevel | Sort-Object) -join ',') | Should -Be 'My Code Space'
|
||||
}
|
||||
|
||||
@@ -1378,7 +1378,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
$before = (Get-FileHash -LiteralPath $script:E2EManifestPath -Algorithm SHA256).Hash
|
||||
Remove-Item -LiteralPath $script:E2ESource -Recurse -Force
|
||||
|
||||
$run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
$run = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $script:E2EList
|
||||
BackupDir = $script:E2EBackupDir
|
||||
DryRun = $true
|
||||
@@ -1390,7 +1390,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
}
|
||||
|
||||
It 'Restore -WhatIf 同样不写盘' {
|
||||
$run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
$run = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $script:E2EList
|
||||
BackupDir = $script:E2EBackupDir
|
||||
WhatIf = $true
|
||||
@@ -1402,7 +1402,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
It '真实恢复:退出码 0,文件逐字节一致,被排除项没有被恢复出来' {
|
||||
Test-Path -LiteralPath $script:E2ESource | Should -BeFalse # 保证不是空操作
|
||||
|
||||
$run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
$run = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $script:E2EList
|
||||
BackupDir = $script:E2EBackupDir
|
||||
Force = $true
|
||||
@@ -1420,7 +1420,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
}
|
||||
|
||||
It '真实恢复之后 manifest 才被更新(lastRestoreAt)' {
|
||||
$manifest = Read-BaknretManifest -Path $script:E2EManifestPath
|
||||
$manifest = Read-BakNRetManifest -Path $script:E2EManifestPath
|
||||
$record = $manifest.items[$script:E2EArchive.BaseName]
|
||||
$record.lastRestoreAt | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
@@ -1428,7 +1428,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
It '孤儿归档会被点名报告,但不影响退出码' {
|
||||
Copy-Item -LiteralPath $script:E2EArchive.FullName -Destination (Join-Path $script:E2EBackupDir 'Orphaned-Archive.7z') -Force
|
||||
|
||||
$run = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
$run = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $script:E2EList
|
||||
BackupDir = $script:E2EBackupDir
|
||||
QuietTool = $true
|
||||
@@ -1440,7 +1440,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
}
|
||||
|
||||
It '带 -Only 时不做孤儿审计(避免把未选中的归档误报成孤儿)' {
|
||||
$run = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
$run = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $script:E2EList
|
||||
BackupDir = $script:E2EBackupDir
|
||||
Only = @('My Code Space')
|
||||
@@ -1451,20 +1451,20 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' -
|
||||
|
||||
It '源路径不存在时记为 missing-source,退出码仍为 0(跳过不算失败)' {
|
||||
$missingList = Write-ListFile -Path (Join-Path $script:E2ERoot 'missing.txt') -Content "Z:\definitely-not-here-12345`n"
|
||||
$run = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
$run = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $missingList
|
||||
BackupDir = $script:E2EBackupDir
|
||||
QuietTool = $true
|
||||
}
|
||||
|
||||
$run.ExitCode | Should -Be 0
|
||||
$manifest = Read-BaknretManifest -Path $script:E2EManifestPath
|
||||
$manifest = Read-BakNRetManifest -Path $script:E2EManifestPath
|
||||
$manifest.items['definitely-not-here-12345_from_Z_'].action | Should -Be 'missing-source'
|
||||
}
|
||||
|
||||
It '归档名重复时直接报失败(退出码 1),不静默互相覆盖' {
|
||||
$duplicateList = Write-ListFile -Path (Join-Path $script:E2ERoot 'dup.txt') -Content "$script:E2ESource`n$script:E2ESource`n"
|
||||
$run = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
$run = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $duplicateList
|
||||
BackupDir = $script:E2EBackupDir
|
||||
Force = $true
|
||||
@@ -1493,7 +1493,7 @@ Describe '集成:方向标记与孤儿审计' -Skip:(-not ($script:HasSevenZip
|
||||
|
||||
# 正确方向:先正常备份出归档
|
||||
Write-ListFile -Path $script:DirectionList -Content "$script:DirectionSource`n" | Out-Null
|
||||
$null = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
$null = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $script:DirectionList
|
||||
BackupDir = $script:DirectionBackupDir
|
||||
Force = $true
|
||||
@@ -1507,7 +1507,7 @@ Describe '集成:方向标记与孤儿审计' -Skip:(-not ($script:HasSevenZip
|
||||
|
||||
# 行首 -:仅恢复,备份端应当跳过它,但要把它算作"有主"
|
||||
Write-ListFile -Path $script:DirectionList -Content "- $script:DirectionSource`n" | Out-Null
|
||||
$script:BackupDirectionRun = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
$script:BackupDirectionRun = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $script:DirectionList
|
||||
BackupDir = $script:DirectionBackupDir
|
||||
QuietTool = $true
|
||||
@@ -1516,7 +1516,7 @@ Describe '集成:方向标记与孤儿审计' -Skip:(-not ($script:HasSevenZip
|
||||
# 行首 +:仅备份,恢复端应当跳过它,同样要登记归档名
|
||||
Remove-Item -LiteralPath $script:DirectionSource -Recurse -Force
|
||||
Write-ListFile -Path $script:DirectionList -Content "+ $script:DirectionSource`n" | Out-Null
|
||||
$script:RestoreDirectionRun = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
$script:RestoreDirectionRun = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $script:DirectionList
|
||||
BackupDir = $script:DirectionBackupDir
|
||||
Force = $true
|
||||
|
||||
Reference in new issue
Block a user