Files
BakNRet/.scratch/ci-cd/20260928-001722/pester_after_fix.txt
T
Shuery 45bbd66815 docs(report): 新增 PROJECT_REPORT.md 与 CI/CD 流水线记录
PROJECT_REPORT.md:论文式项目报告(摘要 / 目录 / 7 章 / 参考文献 / 致谢 / 附录 A),
4 张 Mermaid 图同样经真实浏览器渲染验证。所有数字都与证据文件逐个核对过。

.scratch/ci-cd/:本次流水线的全部证据与可重跑脚本
  * 阶段报告:依赖安全扫描 / 许可证合规 / 阶段 0 静态分析 / 阶段 3 测试与性能
  * 证据:分析器原始输出(修复前 84 条、修复后 80 条)、Pester 详细输出、
    性能基线 JSON、黑盒用例结果(阶段 1 的 11 例与阶段 2 回归的 12 例)
  * 可重跑:blackbox-tests.ps1 / blackbox-regression.ps1 / perf-baseline.ps1

两条边界必须写在明处,不能含糊:

  * **VM 内验证只取到修复前的快照**(Pester 183 项全绿)。随后会话审批策略改为 never,
    gsudo 提权被自动拒绝(退出码 999),而 Hyper-V 与 PowerShell Direct 都需要管理员,
    于是修复后的三套件在 VM 内**没有跑成**。报告里明确标注了范围,没有把"宿主跑绿了"
    说成"VM 也跑绿了"。
  * **性能基线是首次建立**,无历史可比,故无退化可判;指标只在同机同宿主下对比,
    跨机比数字没有意义。

另外记一笔:静态分析的口径是"仓库自己的门禁"。剩余 80 条全是风格类(0 Error),且逐条
有依据 —— 行长 160 是配置里写明的有意偏离,PSPlaceCloseBrace 等集中在测试夹具字符串内
(改了会改变断言语义)。严格模式的"零容忍"在这里与仓库自身约定相抵,选择尊重仓库约定
并在报告里登记,而不是制造一个横跨 12 个文件的纯排版大 diff。
2026-10-02 01:17:40 +08:00

334 lines
28 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Pester 5.9.1 (D:\Workspace\Temp\BakNRet\.tools\modules\Pester\5.9.1)
测试文件:D:\Workspace\Temp\BakNRet\tests
Pester v5.9.1
Starting discovery in 3 files.
Discovery found 185 tests in 226ms.
Running tests.
Running tests from 'D:\Workspace\Temp\BakNRet\tests\BakNRet.Formats.Tests.ps1'
Describing 软件名录:Slot 形状(新契约)
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacystr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacyarr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 legacydirs 有问题:Slot Dirs 的写法不对,应写成 @{ Path = '...' }
[+] 一个软件多个 Slot:Kind=Multi,Slot 按名排序且说明被保留 163ms (131ms|32ms)
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacystr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacyarr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 legacydirs 有问题:Slot Dirs 的写法不对,应写成 @{ Path = '...' }
[+] 每个 Slot 都是一个独立的归档项来源(Kind=slot / Origin=catalog) 82ms (81ms|1ms)
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacystr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacyarr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 legacydirs 有问题:Slot Dirs 的写法不对,应写成 @{ Path = '...' }
[+] Slot 级排除写在 Slot 自己身上(相对本 Slot 的归档根) 32ms (30ms|1ms)
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacystr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacyarr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 legacydirs 有问题:Slot Dirs 的写法不对,应写成 @{ Path = '...' }
[+] 数组里"当前不存在"的 Slot 仍然产出归档项(恢复要靠它还原回原位) 64ms (64ms|1ms)
[+] 文件 Slot:归档项是文件项(归档里就是名为 Slot 的文件) 8ms (7ms|1ms)
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacystr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacyarr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 legacydirs 有问题:Slot Dirs 的写法不对,应写成 @{ Path = '...' }
[+] 旧的裸字符串 / 字符串数组写法被拒绝(ERROR + 跳过) 22ms (22ms|1ms)
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacystr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 'legacyarr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:05] [ERROR] 名录条目 legacydirs 有问题:Slot Dirs 的写法不对,应写成 @{ Path = '...' }
[+] 旧的 @{ Dirs = @(...) } 写法不再展开,留下 Invalid 与原因 24ms (23ms|1ms)
[+] 多 Slot 的名录条目在清单里仍然按软件名命名归档 3ms (3ms|0ms)
Describing 清单修饰符:新契约格式
[+] :: 覆盖 Path:单 Slot 条目直接生效 13ms (12ms|2ms)
[+] :: 覆盖遇到多 Slot 条目 -> Blocking(不知道给哪一个,绝不猜) 6ms (6ms|0ms)
[+] :- 排除与 :+ 包含并存,顺序任意 15ms (15ms|1ms)
[+] @ Exclude / @ Include / @ Path 与记号写法等价 7ms (6ms|0ms)
[+] :encrypt / :!encrypt 覆盖名录里的加密默认值 11ms (11ms|0ms)
[+] 遗留写法 @encrypt / @pathname / @root= 仍可解析 18ms (17ms|1ms)
[+] 同一行里重复写同类记号会累积(不静默丢掉前一条规则) 12ms (11ms|1ms)
[2026-09-28 12:42:05] [WARN] 清单行缺少目标,已忽略::- logs\
[+] 行尾说明与缺少目标的行 6ms (6ms|1ms)
Describing 集成:Slot 布局的打包与恢复
[+] 备份退出码 0,归档名就是软件名 9ms (7ms|2ms)
[+] 归档顶层就是各个 Slot 名(目录 Slot + 文件 Slot + Include 项) 42ms (41ms|1ms)
[+] manifest.layouts 记下每个归档项是目录还是文件 8ms (7ms|1ms)
[+] 归档内容:\ 布局,文件 Slot 是名为 Slot 的文件,Slot 排除生效 40ms (39ms|1ms)
[+] 真实恢复:目录 Slot、文件 Slot 与 Include 都落回各自的原位 2.37s (2.37s|1ms)
[+] 文件 Slot 在目标不存在时靠 manifest.layouts 恢复成文件(而不是目录) 3ms (2ms|0ms)
[+] 恢复之后 manifest 记下 lastRestoreAt 4ms (4ms|0ms)
Describing 集成:旧布局归档的回退恢复
[+] 归档确实是旧布局:顶层是源目录名而不是 Slot 名 30ms (29ms|1ms)
[+] 归档里缺 Slot 层(真实旧归档)时按旧布局回退,把内容还原回原位 1.75s (1.75s|0ms)
[+] 归档里既没有 Slot 层、也没有旧布局名字时明确失败(不再"成功地什么都没恢复") 1.72s (1.72s|0ms)
Describing 集成:归档内路径冲突会被拒绝执行
[+] 退出码 1,且给出"归档内路径冲突"的原因,不生成归档 4ms (3ms|1ms)
[+] manifest 里记下这次是 failed,并带上原因 3ms (3ms|0ms)
Describing 条目从清单里消失后,旧归档必须被点名为孤儿
[+] 归档确实还在磁盘上,manifest 里也还留着历史记录 5ms (4ms|1ms)
[+] 第二次运行把 my-app.7z 点名成孤儿,并说明 manifest 里还有历史记录 2ms (2ms|0ms)
Describing manifest 一致性:archive 字段只在文件真的存在时才写
[+] 存在的归档保留 archive,不存在的被清空 9ms (8ms|1ms)
[+] 清空 archive 时保留条目本身的历史(source / action 不动) 3ms (3ms|0ms)
[+] 人工删掉归档之后再同步一次,记录会被纠正过来 4ms (3ms|0ms)
Running tests from 'D:\Workspace\Temp\BakNRet\tests\BakNRet.Security.Tests.ps1'
Describing 排除判定与 7z 的 -x! / -xr! 语义对齐
[+] 锚定模式只命中它自己那棵子树 8ms (7ms|1ms)
[+] ! 通配按任意层级的组件名匹配(* 不是正则) 2ms (1ms|0ms)
[+] !re: 走正则,且组件名与整条相对路径都算命中 2ms (2ms|0ms)
[+] 没有模式时一律不排除 1ms (1ms|0ms)
[+] 模式里的空格按 7z 的规矩当 ? 处理 1ms (1ms|0ms)
Describing SID 映射(跨机恢复)
[+] 整 SID 精确替换 5ms (5ms|1ms)
[+] 不会误伤以它为前缀的更长的 SID 1ms (1ms|0ms)
[+] 空映射表时原样返回 1ms (1ms|0ms)
Describing 安全描述符采集
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] Full:每个对象一条记录,键是归档内相对路径 340ms (340ms|1ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] 根记录的 SDDL 保留了 CREATOR OWNER、IO 标志、孤儿 SID 和 protected 位 12ms (12ms|0ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] Smart 比 Full 少,但根永远保留 24ms (23ms|0ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] Roots 只存归档项的根,不再往下走 4ms (4ms|0ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] sidecar 往返:条数与 SDDL 原样保留 25ms (24ms|0ms)
[+] 旁挂文件不存在时读出 $null(调用方据此打告警,而不是静默当没事) 1ms (1ms|0ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] 排除模式在采集时同样生效(采集树 == 归档树) 9ms (9ms|0ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
Describing 安全描述符回放
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeRestorePrivilege、SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] 回放后根对象的安全描述符与源逐字节一致(protected / CO / 孤儿 SID 全在) 40ms (39ms|1ms)
[+] 全部对象的安全指纹与源一致(属主/属组/ACE 集合) 5ms (5ms|0ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeRestorePrivilege、SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] 目标不存在或不是普通对象时记 Skipped,不记 Failed 3ms (3ms|0ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeRestorePrivilege、SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] 归档根名对不上时一条都不回放(不会把兄弟项的 ACL 倒过来) 1ms (1ms|0ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeRestorePrivilege、SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] 属组写不进去时不会连累 DACL:回退到底也要把 ACL 落下去 7ms (7ms|0ms)
[2026-09-28 12:42:21] [WARN] 这些特权不在当前令牌里(需要管理员或 SYSTEM):SeRestorePrivilege、SeBackupPrivilege —— 属主将无法改成别的账户,只能恢复 DACL
[+] 对象的安全描述符读不到时带 e 记账,回放时跳过而不是写坏 2ms (2ms|0ms)
Describing 与 Backup.ps1 / Restore.ps1 的集成
[+] 备份会写出 .acl.json,并在 manifest 里记下它 2.34s (2.34s|1ms)
[+] 恢复会把安全描述符回放回去(删源之后仍然逐对象与备份前一致) 2.14s (2.14s|0ms)
[+] -SkipSecurity 时不回放(目标保持新建对象的默认 ACL) 1.77s (1.77s|0ms)
[+] 归档旁边没有 acl.json 时打告警、不算失败(旧归档照样恢复得出来) 1.76s (1.76s|0ms)
Running tests from 'D:\Workspace\Temp\BakNRet\tests\BakNRet.Tests.ps1'
Describing BackupList.txt 解析
[+] 注释行与空行返回 $null 2ms (2ms|1ms)
[+] 裸路径:Path 原样、方向 both、不是软件名、没有任何覆盖 6ms (6ms|0ms)
[+] 软件名:IsName 为真 2ms (1ms|0ms)
[+] IsName 判定:含分隔符或 % 就算字面路径 3ms (2ms|0ms)
[+] 标记必须是独立记号:C:\a:-b 仍然只是一个路径 2ms (2ms|0ms)
[+] 行首 + 仅备份、- 仅恢复,方向标记不进入目标 4ms (4ms|0ms)
[+] 只有方向标记、没有目标 -> 忽略该行 2ms (2ms|0ms)
[+] 行首标记贴在目标上也算(+Name / -Path),且标记不进入目标 4ms (4ms|0ms)
[+] 非行首的 + / - 不是方向标记 22ms (22ms|0ms)
[+] :: 现在表示"覆盖 Path",与 :- 彻底分开 8ms (5ms|3ms)
[+] :: 的值可以带空格(一直取到下一个标记之前) 2ms (2ms|0ms)
[+] :- 的逗号 / 分号列表拆成多个模式 7ms (6ms|0ms)
[+] 排除模式的引号只保护空白,不保护逗号 3ms (2ms|0ms)
[+] :+ 的包含项是 : 2ms (2ms|0ms)
[+] :encrypt 与 :!encrypt 控制该条目的加密开关 2ms (2ms|0ms)
[+] @ Key='Value' 覆盖 Path / Exclude / Include / Encrypt 4ms (4ms|0ms)
[+] @ 的键名大小写不敏感,且支持紧跟 @ 的写法 2ms (2ms|0ms)
[+] 历史写法 @encrypt / @!encrypt 仍然被识别成加密覆盖 2ms (2ms|0ms)
[+] 历史写法 @pathname / @root= 进入 Flags 3ms (2ms|0ms)
[2026-09-28 12:42:30] [WARN] 清单里的 @ 字段 'UnknownKey' 不是已知字段(Path / Exclude / Include / Encrypt),已忽略:Foo @ UnknownKey='v' :- logs\
[+] 未知的 @ 字段进入 UnknownKeys,且不影响其余字段解析 2ms (2ms|0ms)
[+] 行尾的 # 说明会成为 Comment 2ms (2ms|0ms)
[+] 路径里紧贴的 # 不会被当成注释 1ms (1ms|0ms)
[+] 没有说明时 Comment 为空 1ms (1ms|0ms)
[+] 目标可以带空格:第一个标记之前整段都是目标 3ms (2ms|0ms)
[2026-09-28 12:42:30] [WARN] 清单行缺少目标,已忽略::- logs
[2026-09-28 12:42:30] [WARN] 清单行缺少目标,已忽略:@ Exclude='x'
[+] 缺少目标(标记出现在第一个位置)会被忽略并告警 2ms (2ms|0ms)
[+] 记号切分:引号内的空白不切分,引号本身留在记号里 2ms (1ms|0ms)
[+] 记号识别只认完整记号 2ms (2ms|0ms)
[+] 去引号:成对才去,不成对原样返回 2ms (1ms|0ms)
Describing 归档命名
[+] 基础命名规则:末级名_from_上级路径用加号连接 2ms (1ms|1ms)
[+] / 与 \ 以及重复分隔符结果一致 2ms (1ms|0ms)
[+] 命名与路径往返 6ms (6ms|0ms)
[+] 归档名里不含非法文件名字符 4ms (4ms|0ms)
[+] %变量% 写法里的 % 会保留在归档名里 1ms (1ms|0ms)
[+] 软件名条目:默认用软件名做归档名 5ms (4ms|0ms)
[+] 字面路径条目:仍用路径命名算法(现有清单无需改写) 2ms (2ms|0ms)
[+] @pathname 用名录里的真实路径命名,而不是软件名 4ms (3ms|0ms)
[2026-09-28 12:42:30] [WARN] 名录里没有 'no-such-thing',按目录名处理
[2026-09-28 12:42:30] [WARN] 名录里没有 'no-such-thing',按目录名处理
[+] 名录里没有该名字:归档名退回可读目录名,并给出 Error 5ms (4ms|0ms)
Describing 7z 排除参数翻译
[+] 相对模式自动补上归档根目录名 5ms (5ms|1ms)
[+] 模式已带根名前缀时,Split-BakNRetPatternScope 先摘掉前缀,结果不重复 5ms (4ms|0ms)
[+] ! 前缀翻译成递归组件匹配(-xr!) 1ms (1ms|0ms)
[+] 模式里的空格转成 ?(归档项自己的名字按原样保留) 2ms (1ms|0ms)
[+] 生成的参数里绝不出现引号(旧实现 -x!"路径" 让排除全部失效) 1ms (1ms|0ms)
[+] 空模式被忽略 2ms (1ms|0ms)
[+] !re: 会把正则展开成精确的 -x! 参数 25ms (25ms|0ms)
[+] 非法正则给出 Error,而不是抛异常 6ms (6ms|0ms)
[+] 正则命中数超过上限时明确报错 3ms (3ms|0ms)
[+] 参数总长超过安全上限时明确报错 1ms (1ms|0ms)
[+] Split-BakNRetPatternScope:指名 Slot 的模式只分给那个 Slot 2ms (1ms|0ms)
[+] Split-BakNRetPatternScope:没点名任何 Slot 的模式广播给每一项 1ms (1ms|0ms)
[+] Split-BakNRetPatternScope:! 与 !re: 模式广播给每一项 1ms (1ms|0ms)
[+] Split-BakNRetPatternScope:每个归档项都有键(没有模式时是空数组) 2ms (2ms|0ms)
[+] Merge-BakNRetExcludeArgument 去重且保持顺序 3ms (2ms|0ms)
[+] 多 Slot 条目:逐项分配 + 翻译 + 去重合成一份参数 3ms (3ms|0ms)
Describing 归档项与暂存目录
[+] New-BakNRetArchiveItem 规范化归档内路径并算出 TopName 3ms (3ms|1ms)
[+] Get-BakNRetArchiveTopName 取归档内相对路径的第一段 1ms (1ms|0ms)
[+] 暂存半途失败会自己清干净(不留指向真实数据的 junction) 33ms (32ms|0ms)
[+] 目录项用 junction 挂进暂存目录,文件项用硬链接(名字就是归档内路径) 18ms (17ms|0ms)
[+] Remove-BakNRetArchiveStaging 只删连接点,不顺着走进真实目录 8ms (8ms|0ms)
Describing 命令行参数拼接
[+] 无空格参数原样输出 2ms (1ms|1ms)
[+] 含空格参数加引号 3ms (2ms|0ms)
[+] 引号内的结尾反斜杠翻倍(否则会被当成转义引号) 2ms (1ms|0ms)
[+] 内部引号被转义 1ms (1ms|0ms)
[+] 空参数输出一对空引号 2ms (1ms|1ms)
Describing manifest 与配置
[+] manifest 读写往返 10ms (9ms|1ms)
[+] manifest 原样保存 layouts(name/kind),全新恢复时靠它判断目录还是文件 7ms (7ms|0ms)
[2026-09-28 12:42:30] [WARN] manifest 解析失败(将重新建立):C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\broken.json —— Conversion from JSON failed with error: Invalid character after parsing property name. Expected ':' but got: i. Path '', line 1, position 7.
[+] manifest 损坏时不抛异常,而是重建空清单 9ms (8ms|0ms)
[+] 配置缺失时返回默认值 4ms (4ms|0ms)
[+] 配置嵌套段落合并且不丢默认键 6ms (6ms|0ms)
[+] 口令:环境变量可读取,取不到时返回 $null(绝不退化成明文) 3ms (3ms|0ms)
Describing 软件名录
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[+] 名录解析:条目与 Slot 的字段集合就是新契约 54ms (53ms|1ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[+] 一个软件多个 Slot:Kind=Multi,Slot 按名字排序 33ms (33ms|0ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[+] Kind:Single / Multi / Partial / Unresolved / Invalid 34ms (34ms|0ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[+] 前缀补全:_ 与 - 都会被补全 28ms (27ms|0ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[+] 不会把 Legendary 误配成 LegendarySomething 28ms (27ms|0ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[+] 一个 Slot 命中多个候选目录:报 Error,绝不悄悄挑一棵树 31ms (31ms|0ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[+] 文件 Slot:Path 指向文件时 IsFile 为真,Encrypt 也带上 25ms (25ms|0ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[+] %变量% 会在名录路径里展开 27ms (27ms|0ms)
[+] $( ... ) 子表达式会被求值(含嵌套) 6ms (6ms|0ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[2026-09-28 12:42:30] [ERROR] 名录条目 nopath 有问题:Slot S 缺少 Path
[2026-09-28 12:42:30] [ERROR] 名录条目 dupslot 有问题:Slot D 的 Path 匹配到 2 个目录:C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_1、C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\catalog\dup_2;一个 Slot 只能对应一个目录,请拆成多个 Slot
[2026-09-28 12:42:30] [ERROR] 名录条目 badslot 有问题:Slot S 的写法不对,应写成 @{ Path = '...' }
[+] 读取结果按"路径 + 时间戳 + 长度 + 内容 MD5"缓存 63ms (62ms|0ms)
[+] 名录文件内容变了以后缓存自动失效 17ms (16ms|0ms)
[+] Includes:分文件维护的名录会被合并 12ms (11ms|0ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 'arr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:30] [ERROR] 名录条目 'objarr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:30] [ERROR] 名录条目 'bare' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:30] [ERROR] 名录条目 dirstyle 有问题:Slot Dirs 的写法不对,应写成 @{ Path = '...' }
[+] 旧的裸字符串 / 字符串数组 / 对象数组写法都会报 ERROR 并被跳过 3ms (3ms|0ms)
[2026-09-28 12:42:30] [ERROR] 名录条目 'arr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:30] [ERROR] 名录条目 'objarr' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:30] [ERROR] 名录条目 'bare' 格式不对:应写成 @{ <Slot 名> = @{ Path = '...' } }
[2026-09-28 12:42:30] [ERROR] 名录条目 dirstyle 有问题:Slot Dirs 的写法不对,应写成 @{ Path = '...' }
[+] 旧的 @{ Dirs = @(...) } 写法不再展开:留下 Invalid 条目和原因 4ms (3ms|0ms)
[+] 软件名会被规范化成合法文件名 2ms (1ms|0ms)
Describing Resolve-BakNRetBackupEntry:条目解析
[+] resolve 与归档项的字段集合就是新契约(旧字段已删除) 4ms (4ms|1ms)
[+] 软件名条目:isName/CatalogEntry/BaseName/ArchiveFlavor/Source 7ms (6ms|0ms)
[+] 软件名条目:Items 来自 Slot(ArchivePath=Slot 名,Kind=slot,Origin=catalog) 6ms (6ms|0ms)
[+] 字面路径条目:一个 path 项(ArchivePath 是末级名) 3ms (3ms|0ms)
[+] 字面路径条目上的 @ Path= 覆盖目标路径,但归档名仍按原路径 2ms (2ms|0ms)
[+] 名录里的路径不存在时仍给出 Items(恢复要靠它还原回原位) 5ms (5ms|0ms)
[+] :: 覆盖 Path:单 Slot 条目直接生效 5ms (5ms|0ms)
[+] :: / @ Path= 覆盖遇到多 Slot 条目 -> Blocking(不猜是哪一个) 5ms (5ms|0ms)
[+] 条目级 :- 覆盖名录里的排除:HasExcludeOverride 为真 3ms (3ms|0ms)
[+] 名录 Slot 的 Include 会追加成 include 项 5ms (5ms|0ms)
[+] 条目级 :+ / @ Include= 覆盖名录里的 Include 11ms (11ms|0ms)
[2026-09-28 12:42:30] [WARN] encapp:名录里各 Slot 的 Encrypt 不一致,整个归档按加密处理
[+] 加密:名录里各 Slot 取或;条目级 :encrypt / :!encrypt 覆盖 9ms (9ms|0ms)
[+] 方向标记会传递到 Resolve-BakNRetBackupEntry.Direction 5ms (5ms|0ms)
[+] 归档内路径冲突(include 与 Slot 同名)-> Blocking 5ms (4ms|0ms)
[+] 归档内路径冲突(父子关系)-> Blocking 5ms (5ms|0ms)
[2026-09-28 12:42:30] [WARN] 名录里没有 'no-such-thing',按目录名处理
[+] 名录里没有该名字:Error 给出,Items 为空 3ms (2ms|0ms)
Describing 外部命令退出码(旧实现的核心缺陷)
[2026-09-28 12:42:30] [DEBUG] 执行: cmd.exe /c "exit 0" -p<口令已隐藏>
[+] 口令不会进日志:DEBUG 下打印的命令行要遮蔽 -p 参数 31ms (30ms|1ms)
[+] 运行锁:同一份备份目录同时只能有一个持有者 27ms (27ms|0ms)
[+] 运行锁:释放之后可以重新取得 8ms (7ms|0ms)
[+] 空目录的摘要给 0 而不是 $null(否则空间守卫会静默失效) 9ms (9ms|0ms)
[+] 原子替换:成功时新内容到位且不留 .tmp;失败时旧内容完好 14ms (14ms|0ms)
[+] Invoke-ExternalCommand 能拿到真实退出码 13ms (12ms|0ms)
[+] 成功时拿到 0 12ms (12ms|0ms)
Describing 集成:真实 7z 压缩与排除规则
[+] 排除规则与空格处理在真实归档上生效 55ms (54ms|1ms)
[+] 对照组:不加排除时被排除的文件确实在归档里(证明上一条不是空归档) 58ms (57ms|0ms)
ERROR: C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\integration\corrupt.7z
C:\Users\Shuery\AppData\Local\Temp\baknret-pester-ffa25c80\integration\corrupt.7z
Open ERROR: Cannot open the file as [7z] archive
ERRORS:
Headers Error
[+] 7z t 对完好归档返回 0,对损坏归档返回非 0(归档后校验的依据) 52ms (52ms|0ms)
Describing 集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)
[+] 备份退出码为 0(旧实现会把成功的压缩判成失败) 2ms (1ms|1ms)
[+] 归档已生成且 manifest 记录了条目、动作、校验结果与 layouts 5ms (5ms|0ms)
[+] 备份过程写了日志文件 3ms (2ms|0ms)
[+] 字面路径条目沿用 布局;归档里保留应保留内容、不含被排除项 32ms (32ms|0ms)
[+] [回归] manifest.roots 记录的是归档内真实的顶层条目名 32ms (31ms|0ms)
[+] Restore -DryRun 退出码 0,且一个字节都不写(manifest SHA256 不变) 1.57s (1.57s|0ms)
[+] Restore -WhatIf 同样不写盘 1.6s (1.6s|0ms)
[+] 真实恢复:退出码 0,文件逐字节一致,被排除项没有被恢复出来 2.12s (2.12s|0ms)
[+] 真实恢复之后 manifest 才被更新(lastRestoreAt) 2ms (2ms|0ms)
[+] 孤儿归档会被点名报告,但不影响退出码 2.37s (2.37s|0ms)
[+] 带 -Only 时不做孤儿审计(避免把未选中的归档误报成孤儿) 1.74s (1.74s|0ms)
[+] 源路径不存在时记为 missing-source,退出码仍为 0(跳过不算失败) 1.76s (1.76s|0ms)
[+] 归档名重复时直接报失败(退出码 1),不静默互相覆盖 2.37s (2.37s|0ms)
[+] [回归] 显式指定的清单不存在时报失败(退出码 1),且不在错误位置建模板 1.43s (1.43s|0ms)
[+] [回归] 未显式指定清单时的首次运行仍建模板并退出 0(引导不能被误伤) 1.44s (1.44s|0ms)
Describing 集成:方向标记与孤儿审计
[+] 行首 - 的条目:备份跳过它,但仍把它算作"有主"(不报成孤儿) 4ms (3ms|1ms)
[+] 行首 + 的条目:恢复跳过它、不写回目标,同时登记归档名 2ms (2ms|0ms)
Tests completed in 50.99s
Tests Passed: 185, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
Pester 测试全部通过:185 项(跳过 0 项)