Files
luo jiyinandfanlusky acc25f40e6 fix(installer): 在最终数据目录保存云候选选择 (#972)
* 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>
2026-10-15 23:00:34 +08:00

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
}