引入软件名录:清单写软件名,归档名也用软件名
新功能 - 新增 SoftwareCatalog.psd1 —— "软件名 -> 目录"映射表,BackupList.txt 里 直接写软件名即可,归档名也就是软件名(FooClolor.7z), 不再是 FooClolor_from_C_+Programs.7z 这种由路径拼出来的名字。 - 三种写法可混用:软件名、字面路径(现有清单无需改写)、软件名 @pathname。 - 名录支持前缀补全(legendary -> legendary_2.0.4,只认 <名>_* / <名>-*)、 Variants(同名目录在多处)、Includes(分文件维护)。 - tools/Rename-Archives.ps1:存量归档重命名,默认试运行,逐份大小校验并重建 manifest。 - 归档名重复直接报错,不再静默互相覆盖。 两套测试全绿:单元 42 项、端到端 23 项(新增名录命名/解析/迁移用例)。 过程中修掉的缺陷 - Resolve-BackupEntry 里 @pathname 与 Unresolved 分支顺序错误, @pathname 会被静默吃掉(改名后仍用软件名)。 - 源目录被删除时解析器丢掉 Sources,导致恢复端把软件名当路径、 报 "Cannot bind argument to parameter 'Path' because it is an empty string"。 恢复的语义恰恰是"源不存在就要还原回去",现在 Sources 照旧给出。 - 源存在性检查曾被漏掉,Get-Item 对不存在路径抛异常会中断整轮备份; 且不能用 Join-Path 探测——目标盘符不存在时它会直接抛异常。 - 计划任务脚本外的 Caller 需要 -DryRun 才能验,已实跑确认。
This commit is contained in:
1 parent
dbc0c00554
commit
045d51ac9c
9 files changed
+1330
-143
No files matched your search
+88
-1
@@ -253,7 +253,94 @@ Test-Case 'Backup -DryRun:退出码 0、不写归档也不写 manifest' {
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 6. 失败路径:源不存在时必须留下可核对的记录
|
||||
# 6. 软件名录:清单里写软件名,归档名就是软件名
|
||||
# ============================================================================
|
||||
|
||||
$catRoot = Join-Path $WorkRoot 'catalog'
|
||||
$catSource = Join-Path $catRoot 'src'
|
||||
$catTarget = Join-Path $catSource 'My App' # 真实目录名与软件名刻意不同
|
||||
$catBackupDir = Join-Path $catRoot 'Backups'
|
||||
$catFile = Join-Path $catRoot 'SoftwareCatalog.psd1'
|
||||
$catList = Join-Path $catRoot 'list.txt'
|
||||
$catConfig = Join-Path $catRoot 'config.psd1'
|
||||
|
||||
New-Item -ItemType Directory -Path $catTarget -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $catTarget 'data.txt') -Value 'catalog-test' -Encoding UTF8
|
||||
Set-Content -LiteralPath (Join-Path $catTarget 'skip.bin') -Value 'nope' -Encoding UTF8
|
||||
|
||||
[System.IO.File]::WriteAllText($catFile, "@{`n 'my-app' = '$catTarget'`n}`n", [System.Text.UTF8Encoding]::new($false))
|
||||
[System.IO.File]::WriteAllText($catList, "my-app :: skip.bin`n", [System.Text.UTF8Encoding]::new($false))
|
||||
[System.IO.File]::WriteAllText($catConfig, "@{ SoftwareCatalog = '$catFile' }`n", [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
& $backupScript -BackupListPath $catList -BackupDir $catBackupDir -ConfigPath $catConfig -Force -QuietTool
|
||||
$catExitCode = $LASTEXITCODE
|
||||
$catArchive = Join-Path $catBackupDir 'my-app.7z'
|
||||
|
||||
Test-Case '清单里写软件名 -> 归档名就是软件名' {
|
||||
Assert-Equal 0 $catExitCode
|
||||
Assert-FileExists $catArchive
|
||||
}
|
||||
|
||||
Test-Case '软件名条目的归档内容与历史布局一致(根目录仍是源目录名)' {
|
||||
$extract = Join-Path $catRoot 'verify'
|
||||
New-Item -ItemType Directory -Path $extract -Force | Out-Null
|
||||
$sevenZip = Get-Command 7z -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Source
|
||||
if ($sevenZip) {
|
||||
$null = Invoke-ExternalCommand -FilePath $sevenZip -ArgumentList @('x', '-bso0', '-bsp0', '-y', "-o$extract", $catArchive)
|
||||
# 归档文件名是软件名 my-app,但包内根目录是源目录名 My App
|
||||
Assert-FileExists (Join-Path $extract 'My App\data.txt')
|
||||
Assert-FileMissing (Join-Path $extract 'my-app') '包内不应多出一层软件名'
|
||||
Assert-FileMissing (Join-Path $extract 'My App\skip.bin') '排除模式以源目录名为前缀,仍然生效'
|
||||
}
|
||||
}
|
||||
|
||||
Test-Case '@pathname 覆盖:强制用路径命名算法(用独立备份目录,避免污染共享 manifest)' {
|
||||
$pathList = Join-Path $catRoot 'list-pathname.txt'
|
||||
$pathNameDir = Join-Path $catRoot 'Backups-pathname'
|
||||
[System.IO.File]::WriteAllText($pathList, "my-app @pathname`n", [System.Text.UTF8Encoding]::new($false))
|
||||
& $backupScript -BackupListPath $pathList -BackupDir $pathNameDir -ConfigPath $catConfig -Force -QuietTool
|
||||
Assert-Equal 0 $LASTEXITCODE
|
||||
|
||||
# 名录里存的是绝对路径,所以路径命名结果也基于它
|
||||
$expectedBase = Get-BackupBaseName -RawPath $catTarget
|
||||
Assert-FileExists (Join-Path $pathNameDir ($expectedBase + '.7z'))
|
||||
}
|
||||
|
||||
Test-Case '软件名录条目:删源后能按原路径恢复' {
|
||||
Remove-Item -LiteralPath $catTarget -Recurse -Force
|
||||
& $restoreScript -BackupListPath $catList -BackupDir $catBackupDir -ConfigPath $catConfig -Force
|
||||
Assert-Equal 0 $LASTEXITCODE
|
||||
Assert-FileExists (Join-Path $catTarget 'data.txt')
|
||||
Assert-Equal 'catalog-test' (Get-Content -LiteralPath (Join-Path $catTarget 'data.txt') -Raw).Trim()
|
||||
}
|
||||
|
||||
|
||||
Test-Case '归档名重复时直接报失败,不静默互相覆盖' {
|
||||
$dupList = Join-Path $catRoot 'dup.txt'
|
||||
[System.IO.File]::WriteAllText($dupList, "my-app`nmy-app`n", [System.Text.UTF8Encoding]::new($false))
|
||||
& $backupScript -BackupListPath $dupList -BackupDir $catBackupDir -ConfigPath $catConfig -Force -QuietTool
|
||||
Assert-Equal 1 $LASTEXITCODE '重复条目应让退出码为 1'
|
||||
}
|
||||
|
||||
Test-Case '字面路径不受名录影响,仍走路径命名' {
|
||||
$literalList = Join-Path $catRoot 'list-literal.txt'
|
||||
[System.IO.File]::WriteAllText($literalList, "$catTarget`n", [System.Text.UTF8Encoding]::new($false))
|
||||
& $backupScript -BackupListPath $literalList -BackupDir $catBackupDir -ConfigPath $catConfig -Force -QuietTool
|
||||
Assert-Equal 0 $LASTEXITCODE
|
||||
}
|
||||
|
||||
Test-Case '名录里没有该软件名时记为 missing-source,而不是崩掉' {
|
||||
$badList = Join-Path $catRoot 'bad.txt'
|
||||
[System.IO.File]::WriteAllText($badList, "no-such-app`n", [System.Text.UTF8Encoding]::new($false))
|
||||
& $backupScript -BackupListPath $badList -BackupDir $catBackupDir -ConfigPath $catConfig -Force -QuietTool
|
||||
Assert-Equal 0 $LASTEXITCODE '跳过不算失败'
|
||||
$rec = (Read-BaknretManifest -Path (Join-Path $catBackupDir 'manifest.json')).items['no-such-app']
|
||||
Assert-True ($null -ne $rec) '应留下记录'
|
||||
Assert-Equal 'missing-source' $rec.action
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 7. 失败路径:源不存在时必须留下可核对的记录
|
||||
# ============================================================================
|
||||
|
||||
$missingList = Join-Path $WorkRoot 'missing.txt'
|
||||
|
||||
@@ -251,6 +251,105 @@ Test-Case '口令:环境变量可读取,取不到返回 $null' {
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
Write-Host "`n== 软件名录 ==" -ForegroundColor Cyan
|
||||
# ============================================================================
|
||||
|
||||
$catalogSandbox = Join-Path $env:TEMP ("baknret-cat-" + [guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
New-Item -ItemType Directory -Path $catalogSandbox -Force | Out-Null
|
||||
|
||||
Test-Case '名录解析:裸键、引号键、带 - 与 . 的名字' {
|
||||
$catPath = Join-Path $catalogSandbox 'SoftwareCatalog.psd1'
|
||||
$content = "@{`n FooClolor = 'C:\Programs\FooClolor'`n 'scoop-config' = '%UserProfile%\.config\scoop'`n '.ssh' = '%UserProfile%\.ssh'`n}`n"
|
||||
[System.IO.File]::WriteAllText($catPath, $content, [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
$catalog = Get-SoftwareCatalog -Path $catPath
|
||||
Assert-Equal 3 $catalog.Count
|
||||
Assert-True $catalog.ContainsKey('FooClolor')
|
||||
Assert-True $catalog.ContainsKey('scoop-config') '带连字符的键必须能解析'
|
||||
Assert-True $catalog.ContainsKey('.ssh') '带点的键必须能解析'
|
||||
}
|
||||
|
||||
Test-Case '名录解析:目录带版本后缀时按前缀补全(legendary_2.0.4)' {
|
||||
$root = Join-Path $catalogSandbox 'probe'
|
||||
New-Item -ItemType Directory -Path (Join-Path $root 'legendary_2.0.4') -Force | Out-Null
|
||||
$catPath = Join-Path $catalogSandbox 'suffix.psd1'
|
||||
[System.IO.File]::WriteAllText($catPath, "@{`n legendary = '$root\legendary'`n}`n", [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
$catalog = Get-SoftwareCatalog -Path $catPath
|
||||
Assert-Equal 'Single' $catalog['legendary'].Kind
|
||||
Assert-True ($catalog['legendary'].ResolvedPath -like '*legendary_2.0.4') '应补全到实际目录'
|
||||
}
|
||||
|
||||
Test-Case '名录解析:不会把 Legendary 误配成 LegendarySomething' {
|
||||
$root = Join-Path $catalogSandbox 'strict'
|
||||
New-Item -ItemType Directory -Path (Join-Path $root 'LegendarySomething') -Force | Out-Null
|
||||
$catPath = Join-Path $catalogSandbox 'strict.psd1'
|
||||
[System.IO.File]::WriteAllText($catPath, "@{`n Legendary = '$root\Legendary'`n}`n", [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
$catalog = Get-SoftwareCatalog -Path $catPath
|
||||
Assert-Equal 'Unresolved' $catalog['Legendary'].Kind '必须以下一个字符是 _ 或 - 为界'
|
||||
}
|
||||
|
||||
Test-Case '软件名条目:默认用软件名做归档名' {
|
||||
$item = ConvertFrom-BackupListLine -Line 'FooClolor'
|
||||
$resolved = Resolve-BackupEntry -Entry $item -CatalogPath (Join-Path $catalogSandbox 'SoftwareCatalog.psd1')
|
||||
Assert-Equal 'FooClolor' $resolved.BaseName
|
||||
Assert-Equal 'name' $resolved.ArchiveFlavor
|
||||
Assert-True $resolved.IsName
|
||||
}
|
||||
|
||||
Test-Case '字面路径条目:仍用路径命名算法(现有清单无需改写)' {
|
||||
$item = ConvertFrom-BackupListLine -Line 'C:\Programs\FooClolor'
|
||||
$resolved = Resolve-BackupEntry -Entry $item -CatalogPath (Join-Path $catalogSandbox 'SoftwareCatalog.psd1')
|
||||
Assert-False $resolved.IsName
|
||||
Assert-Equal 'path' $resolved.ArchiveFlavor
|
||||
Assert-Equal 'FooClolor_from_C_+Programs' $resolved.BaseName
|
||||
}
|
||||
|
||||
Test-Case '@pathname 用真实路径命名,而不是软件名' {
|
||||
$item = ConvertFrom-BackupListLine -Line 'FooClolor @pathname'
|
||||
$resolved = Resolve-BackupEntry -Entry $item -CatalogPath (Join-Path $catalogSandbox 'SoftwareCatalog.psd1')
|
||||
Assert-Equal 'path' $resolved.ArchiveFlavor
|
||||
Assert-Equal 'FooClolor_from_C_+Programs' $resolved.BaseName
|
||||
}
|
||||
|
||||
Test-Case '名录里没有该名字:BaseName 退回可读目录名,并给出 Error' {
|
||||
$item = ConvertFrom-BackupListLine -Line 'no-such-thing'
|
||||
$resolved = Resolve-BackupEntry -Entry $item -CatalogPath (Join-Path $catalogSandbox 'SoftwareCatalog.psd1')
|
||||
Assert-Equal 'no-such-thing' $resolved.BaseName
|
||||
Assert-True ($null -ne $resolved.Error) '应给出错误说明'
|
||||
Assert-Equal 0 $resolved.Sources.Count
|
||||
}
|
||||
|
||||
Test-Case '名录里的路径不存在时仍给出 Sources(恢复要靠它还原回原位)' {
|
||||
$catPath = Join-Path $catalogSandbox 'gone.psd1'
|
||||
[System.IO.File]::WriteAllText($catPath, "@{`n 'gone-app' = 'C:\definitely\not\here'`n}`n", [System.Text.UTF8Encoding]::new($false))
|
||||
$item = ConvertFrom-BackupListLine -Line 'gone-app'
|
||||
$resolved = Resolve-BackupEntry -Entry $item -CatalogPath $catPath
|
||||
Assert-True ($null -ne $resolved.Error) '备份端据 Error 跳过'
|
||||
Assert-Equal 1 $resolved.Sources.Count '恢复端据 Sources 还原'
|
||||
Assert-Equal 'C:\definitely\not\here' $resolved.Sources[0].SourcePath
|
||||
}
|
||||
|
||||
Test-Case 'Includes:分文件维护的名录会被合并' {
|
||||
$basePath = Join-Path $catalogSandbox 'Base.psd1'
|
||||
$extraPath = Join-Path $catalogSandbox 'Extra.psd1'
|
||||
[System.IO.File]::WriteAllText($extraPath, "@{`n 'extra-app' = 'C:\Programs\Extra'`n}`n", [System.Text.UTF8Encoding]::new($false))
|
||||
[System.IO.File]::WriteAllText($basePath, "@{`n Includes = @('Extra.psd1')`n 'base-app' = 'C:\Programs\Base'`n}`n", [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
$catalog = Get-SoftwareCatalog -Path $basePath
|
||||
Assert-True $catalog.ContainsKey('base-app')
|
||||
Assert-True $catalog.ContainsKey('extra-app') 'Includes 引入的条目也应在'
|
||||
}
|
||||
|
||||
Test-Case '软件名会被规范化成合法文件名' {
|
||||
Assert-Equal 'keep%and&and+plus' (Format-CatalogName -Name 'keep%and&and+plus')
|
||||
Assert-True ((Format-CatalogName -Name 'a/b:c') -notmatch '[/:]') '非法字符应被替换'
|
||||
}
|
||||
|
||||
Remove-Item -LiteralPath $catalogSandbox -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# ============================================================================
|
||||
Write-Host "`n== 集成测试:真的跑 7z,验证排除结果落在文件系统上 ==" -ForegroundColor Cyan
|
||||
# ============================================================================
|
||||
|
||||
Reference in new issue
Block a user