* fix(installer): 在最终数据目录保存云候选选择 安装器在数据目录确定后检查已有配置。升级迁移遇到配置冲突时停止,避免覆盖目标配置。 首次安装以 UTF-8 写入云候选选择,并在写入失败时报告错误。 增加安装时序检查和 Windows 安装测试矩阵。 Refs #931 * test(installer): 用 Inno 探针验证云候选配置写回 主安装器与探针共用云候选配置函数。 Windows CI 执行五个隔离的首次安装和升级场景。 Refs #931 * test(installer): 接受 Inno 配置文件的 CRLF 行尾 测试按完整的 cloud_candidates 行检查结果。失败时打印实际字段值。 Refs #931 * fix(installer): check config migration conflict before stopping IME * test(installer): clear expected conflict exit code * test(installer): narrow source check to migration order * fix(installer): 云候选写回失败不再中断安装,迁移可重试 - 写回「不启用云候选」失败时只记日志并提示,挪到必需的安装后步骤之后 - 两处 config.toml 内容相同时视为上次迁移的残留,允许重试 - config.toml 没搬过去时中止安装,避免新目录装上出厂配置 - 数据目录页就检查配置冲突 - ShouldSkipPage 不再写全局状态 - 探针复用生产的迁移判断与顺序,补齐改目录首次安装、重试和迁移失败场景 --------- Co-authored-by: fanlusky <1730976608@qq.com>
108 lines
5.3 KiB
PowerShell
108 lines
5.3 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version Latest
|
|
|
|
function Resolve-Iscc {
|
|
$command = Get-Command 'ISCC.exe' -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($command) { return $command.Source }
|
|
foreach ($version in @(7, 6)) {
|
|
foreach ($root in @(${env:ProgramFiles(x86)}, $env:ProgramFiles, (Join-Path $env:LOCALAPPDATA 'Programs'))) {
|
|
if (-not $root) { continue }
|
|
$candidate = Join-Path $root "Inno Setup $version\ISCC.exe"
|
|
if (Test-Path -LiteralPath $candidate -PathType Leaf) { return $candidate }
|
|
}
|
|
}
|
|
throw '找不到 ISCC.exe'
|
|
}
|
|
|
|
function Get-FileDigest([string]$Path) {
|
|
[Convert]::ToBase64String([IO.File]::ReadAllBytes($Path))
|
|
}
|
|
|
|
$root = Join-Path ([IO.Path]::GetTempPath()) ('msime-cloud-choice-' + [Guid]::NewGuid().ToString('N'))
|
|
$script = Join-Path $PSScriptRoot 'cloud-choice-probe.iss'
|
|
$binary = Join-Path $root 'cloud-choice-probe.exe'
|
|
New-Item -ItemType Directory -Force $root | Out-Null
|
|
|
|
try {
|
|
& (Resolve-Iscc) "/O$root" $script | Out-Null
|
|
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $binary)) {
|
|
throw 'Cannot compile cloud-choice probe'
|
|
}
|
|
|
|
# SameDir:目标就是原目录(默认位置首次安装或原地升级)。
|
|
# Fail:安装应当停止,两份配置保持原样,日志里有 Message。
|
|
# Lock:安装期间独占打开原目录的 config.toml,让迁移失败。
|
|
$cases = @(
|
|
@{ Name = 'fresh-default'; SameDir = $true; Previous = ''; Target = ''; Choice = '0'; Expected = 'false' },
|
|
@{ Name = 'fresh-moved'; SameDir = $false; Previous = ''; Target = ''; Choice = '0'; Expected = 'false' },
|
|
@{ Name = 'target-existing'; SameDir = $false; Previous = ''; Target = 'true'; Choice = '0'; Expected = 'true' },
|
|
@{ Name = 'upgrade-moved'; SameDir = $false; Previous = 'true'; Target = ''; Choice = '0'; Expected = 'true' },
|
|
@{ Name = 'upgrade-in-place'; SameDir = $true; Previous = 'false'; Target = ''; Choice = '1'; Expected = 'false' },
|
|
@{ Name = 'migration-retry'; SameDir = $false; Previous = 'false'; Target = 'false'; Choice = '1'; Expected = 'false' },
|
|
@{ Name = 'migration-conflict'; SameDir = $false; Previous = 'false'; Target = 'true'; Choice = '0'; Fail = '目标数据目录已有 config.toml' },
|
|
@{ Name = 'migration-locked'; SameDir = $false; Previous = 'false'; Target = ''; Choice = '1'; Fail = '无法把用户配置'; Lock = $true }
|
|
)
|
|
|
|
foreach ($case in $cases) {
|
|
$caseRoot = Join-Path $root $case.Name
|
|
$previous = Join-Path $caseRoot 'previous'
|
|
$target = if ($case.SameDir) { $previous } else { Join-Path $caseRoot 'target' }
|
|
New-Item -ItemType Directory -Force $previous, $target | Out-Null
|
|
$oldConfig = Join-Path $previous 'config.toml'
|
|
$targetConfig = Join-Path $target 'config.toml'
|
|
if ($case.Previous) {
|
|
[IO.File]::WriteAllText($oldConfig, "[general]`ncloud_candidates = $($case.Previous)`n")
|
|
}
|
|
if ($case.Target) {
|
|
[IO.File]::WriteAllText($targetConfig, "[general]`ncloud_candidates = $($case.Target)`n")
|
|
}
|
|
$app = Join-Path $caseRoot 'app'
|
|
$installLog = Join-Path $caseRoot 'install.log'
|
|
$before = @{}
|
|
foreach ($path in @($oldConfig, $targetConfig)) {
|
|
if (Test-Path -LiteralPath $path) { $before[$path] = Get-FileDigest $path }
|
|
}
|
|
|
|
$lock = $null
|
|
if ($case.ContainsKey('Lock')) {
|
|
$lock = [IO.File]::Open($oldConfig, 'Open', 'Read', 'None')
|
|
}
|
|
try {
|
|
& $binary '/VERYSILENT' '/SUPPRESSMSGBOXES' '/NORESTART' "/DIR=$app" "/TARGET=$target" "/PREVIOUS=$previous" "/CLOUD=$($case.Choice)" "/LOG=$installLog" | Out-Null
|
|
$exitCode = $LASTEXITCODE
|
|
}
|
|
finally {
|
|
if ($lock) { $lock.Dispose() }
|
|
}
|
|
|
|
if ($case.ContainsKey('Fail')) {
|
|
if ($exitCode -eq 0) { throw "$($case.Name): setup did not stop" }
|
|
foreach ($path in @($oldConfig, $targetConfig)) {
|
|
$now = if (Test-Path -LiteralPath $path) { Get-FileDigest $path } else { $null }
|
|
if ($now -ne $before[$path]) { throw "$($case.Name): $path changed" }
|
|
}
|
|
if (-not ([IO.File]::ReadAllText($installLog).Contains($case.Fail))) {
|
|
throw "$($case.Name): log has no '$($case.Fail)'"
|
|
}
|
|
$global:LASTEXITCODE = 0
|
|
continue
|
|
}
|
|
if ($exitCode -ne 0) { throw "$($case.Name): setup failed ($exitCode)" }
|
|
if (-not $case.SameDir -and (Test-Path -LiteralPath $oldConfig)) {
|
|
throw "$($case.Name): previous config was not moved"
|
|
}
|
|
$config = [IO.File]::ReadAllText($targetConfig)
|
|
if ($config -notmatch "(?m)^cloud_candidates = $($case.Expected)\r?$") {
|
|
$found = [regex]::Match($config, '(?m)^cloud_candidates[^\r\n]*').Value
|
|
throw "$($case.Name): expected $($case.Expected), found [$found]"
|
|
}
|
|
if ($case.Name -like 'fresh-*' -and $config -notmatch '云候选') {
|
|
throw "$($case.Name): fresh config lost UTF-8 text"
|
|
}
|
|
}
|
|
Write-Host "Cloud choice probe passed $($cases.Count) install scenarios"
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|