Files
BakNRet/tools/lab/payload/lab-fixtures.ps1
T
Shuery 2d26f78d15 fix: 源文件改存 UTF-8 with BOM,让 Windows PowerShell 5.1 真正可用
改造前:全仓 6/6 个源文件在 5.1 上解析失败(README 却承诺支持 5.1)。原因是文件是无 BOM 的
UTF-8,而 5.1 没有 BOM 就按 ANSI 代码页解码源码,中文变乱码、全角问号吃掉引号,整块语法塌掉。
现在 28/28 个文件在 5.1 与 7 上都解析零错误,E2E 36 项在 5.1 上全绿。

顺带修掉一个被 5.1 掩盖的缺陷:带 [CmdletBinding()] 的脚本在 5.1 上,param() 默认值里
拿不到 $PSScriptRoot(实测为空串,7 上正常)。于是 Backup.ps1 / Restore.ps1 在 5.1 上不传
路径参数就报错退出 —— 而计划任务恰恰不传。E2E 之所以看不见,是因为它总是显式传路径。
8 处默认值全部移到 param() 之后的解析段,沿用本仓库对 -BackupDir 一直在用的写法。

新增三条可重放的约定,让编码不再是一次性动作:
  .gitattributes 接管行尾(本机 core.autocrlf=true,会把工作区改成 CRLF 制造伪 diff)
  .editorconfig 用 charset = utf-8-bom 锁住 BOM
  tools\Set-SourceEncoding.ps1 是规范化脚本,tools\Invoke-* 之外的任何改动之后都能重放
  test.ps1 是唯一验收入口:Encode + Parse + Unit + Smoke + E2E,在 7 与 5.1 上各跑一遍

test.ps1 的 Encode 层直接检查"必须有 BOM"这条规则。加它的原因很实际:实测本仓库用的
编辑工具在保存时会悄悄去掉 BOM,而丢了 BOM 的文件只在 5.1 上出错、在 7 上完全正常,
没有这条检查就会一直漏过去。

已知未修(下一步处理):5.1 上 Unit 有 6 项、Smoke 有 1 项失败,全部源于测试夹具写
临时文件时没指定编码(5.1 的 Set-Content 默认 ANSI),与产品代码无关。
2026-09-26 22:05:42 +08:00

127 lines
6.5 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
BakNRet 隔离沙盒的假数据生成器(在 VM 内运行)。
.DESCRIPTION
在 C:\BakNRet-Lab\sources 下造出一批**故意带刺**的源目录,用来在真机语义下压测
Backup.ps1 / Restore.ps1 —— 这些形态在宿主机上不敢随便试:
* 多 Slot 软件目录(Data / Config / Cache 三个子目录,各自可带排除);
* 单文件 Slot(一个 .json 直接当一个 Slot);
* 中文 + 空格 + 点的路径名;
* **真 NTFS 连接点(junction)** —— exFAT 的仓库里造不出来;
* **被占用文件** —— 后台进程持有句柄,验证「有文件没打进归档」的告警路径;
* 长路径(接近 260 字符)与 10 层深目录;
* DefaultExcludes 命中的垃圾文件(Thumbs.db / desktop.ini)与 *.log;
* 空目录;
* 一个约 50 MB 的文件,让归档大小/空间预估有实际数字;
* 一个「源不存在」条目对应的目录(故意不建)。
幂等:默认只在缺失时创建;-Force 会先删掉 sources 重建(删连接点用 rmdir,避免跟进目标)。
#>
[CmdletBinding()]
param(
[string]$Root = 'C:\BakNRet-Lab\sources',
[switch]$Force
)
$ErrorActionPreference = 'Stop'
function New-TextFile {
param([string]$Path, [string]$Content, [int]$Count = 1)
$dir = Split-Path -Parent $Path
if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
if ($Count -le 1) {
Set-Content -LiteralPath $Path -Value $Content -Encoding UTF8
} else {
Set-Content -LiteralPath $Path -Value (1..$Count | ForEach-Object { "$Content #$_" }) -Encoding UTF8
}
}
if ($Force -and (Test-Path -LiteralPath $Root)) {
Write-Host "清除已有沙盒源:$Root"
Get-ChildItem -LiteralPath $Root -Recurse -Force -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Attributes -band [IO.FileAttributes]::ReparsePoint } |
ForEach-Object { cmd /c rmdir "$($_.FullName)" 2>$null }
Remove-Item -LiteralPath $Root -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $Root | Out-Null
# --- 1. 多 Slot 软件目录 -----------------------------------------------------
$appA = Join-Path $Root 'AppMultiSlot'
New-TextFile (Join-Path $appA 'Data\settings.json') '{ "theme": "dark", "slots": 3 }'
New-TextFile (Join-Path $appA 'Data\nested\deep\payload.bin') 'binary-ish-payload' -Count 40
New-TextFile (Join-Path $appA 'Config\app.ini') '[main]'
New-TextFile (Join-Path $appA 'Config\app.ini.bak') '[main] backup copy'
New-TextFile (Join-Path $appA 'Cache\cache-01.tmp') 'cache entry' -Count 20
New-TextFile (Join-Path $appA 'Cache\Thumbs.db') 'junk that DefaultExcludes should drop'
New-TextFile (Join-Path $appA 'Cache\desktop.ini') 'junk that DefaultExcludes should drop'
New-TextFile (Join-Path $appA 'Data\session.log') 'log line that an exclusion should drop' -Count 10
New-TextFile (Join-Path $appA 'Data\node_modules\pkg\index.js') 'module.exports = {}'
New-Item -ItemType Directory -Force -Path (Join-Path $appA 'Data\emptydir') | Out-Null
# --- 2. 单文件 Slot ----------------------------------------------------------
$appB = Join-Path $Root 'AppFileSlot'
New-TextFile (Join-Path $appB 'profile.json') '{ "name": "file-slot", "single": true }'
New-TextFile (Join-Path $appB 'readme.txt') 'file slot 的侧车说明'
# --- 3. 中文 + 空格 + 点的路径 ----------------------------------------------
$appC = Join-Path $Root '软件 目录.甲'
New-TextFile (Join-Path $appC '设置\配置 文件.ini') '中文路径内容'
New-TextFile (Join-Path $appC '数据 备份\记录.txt') '记录内容' -Count 5
# --- 4. 真 NTFS 连接点 -------------------------------------------------------
$realTarget = Join-Path $Root 'AppMultiSlot\Data'
$junction = Join-Path $Root 'JunctionToData'
if (-not (Test-Path -LiteralPath $junction)) {
$null = New-Item -ItemType Junction -Path $junction -Target $realTarget -ErrorAction SilentlyContinue
}
if (Test-Path -LiteralPath $junction) { Write-Host "连接点已建:$junction -> $realTarget" }
# --- 5. 长路径与深目录 -------------------------------------------------------
$cursor = Join-Path $Root 'AppDeep'
1..10 | ForEach-Object { $cursor = Join-Path $cursor "level$_" }
New-TextFile (Join-Path $cursor 'bottom.txt') 'deep content'
Write-Host ("最长路径长度:{0} 字符" -f (Join-Path $cursor 'bottom.txt').Length)
# --- 6. 50 MB 大文件 ---------------------------------------------------------
$bigDir = Join-Path $Root 'AppBig'
$bigFile = Join-Path $bigDir 'blob-50mb.bin'
if (-not (Test-Path -LiteralPath $bigFile)) {
New-Item -ItemType Directory -Force -Path $bigDir | Out-Null
$fs = [IO.File]::Create($bigFile)
try {
$rng = [Random]::new(20260926)
$chunk = [byte[]]::new(1MB)
for ($i = 0; $i -lt 50; $i++) { $rng.NextBytes($chunk); $fs.Write($chunk, 0, $chunk.Length) }
} finally { $fs.Dispose() }
}
Write-Host ("大文件:{0} MB" -f [math]::Round((Get-Item $bigFile).Length/1MB,1))
# --- 7. 被占用文件(后台进程持句柄 90 秒后释放)-----------------------------
$lockDir = Join-Path $Root 'AppLocked'
$lockFile = Join-Path $lockDir 'locked.bin'
New-Item -ItemType Directory -Force -Path $lockDir | Out-Null
New-TextFile $lockFile 'this file is held open by another process'
$holderLines = @(
'$path = $args[0]'
'$fs = [IO.File]::Open($path, ''Open'', ''ReadWrite'', ''None'')'
'try { Start-Sleep -Seconds 90 } finally { $fs.Dispose() }'
)
$holderPath = 'C:\BakNRet-Lab\state\hold-lock.ps1'
Set-Content -LiteralPath $holderPath -Value $holderLines -Encoding UTF8
Start-Process -FilePath 'powershell.exe' -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File',$holderPath,$lockFile) -WindowStyle Hidden
Write-Host "已启动占用者进程(持句柄 90 秒):$lockFile"
# --- 8. 「源不存在」条目对应的目录:故意不建 ---------------------------------
Write-Host '故意不创建 MissingApp(用于验证源缺失只跳过、不失败)'
Write-Host ''
Write-Host '--- 沙盒源清单 ---'
Get-ChildItem -LiteralPath $Root -Force | ForEach-Object {
$files = @(Get-ChildItem -LiteralPath $_.FullName -Recurse -File -Force -ErrorAction SilentlyContinue)
$mb = [math]::Round((($files | Measure-Object Length -Sum).Sum) / 1MB, 2)
" {0,-24} {1,4} 个文件 {2,8} MB 连接点={3}" -f $_.Name, $files.Count, $mb, [bool]($_.Attributes -band [IO.FileAttributes]::ReparsePoint)
}