smoke-installer-directories.ps1 4.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. # Exercise the production NSIS directory transaction with private installation directories.
  2. param(
  3. [Parameter(Mandatory)][string]$Makensis,
  4. [Parameter(Mandatory)][string]$SevenZip,
  5. [string]$FrameLibrary,
  6. [scriptblock]$SignExecutable
  7. )
  8. $ErrorActionPreference = 'Stop'
  9. $Makensis = [System.IO.Path]::GetFullPath($Makensis)
  10. $SevenZip = [System.IO.Path]::GetFullPath($SevenZip)
  11. $scratch = [System.IO.Directory]::CreateTempSubdirectory('dsh-directory-smoke-').FullName
  12. $fixture = Join-Path $PSScriptRoot '../tests/fixtures/installer-directory-smoke.nsi'
  13. function Invoke-Checked([string]$Executable, [string[]]$Arguments) {
  14. & $Executable @Arguments | Out-Host
  15. if ($LASTEXITCODE -ne 0) { throw "$Executable exited with $LASTEXITCODE" }
  16. }
  17. try {
  18. $payload = Join-Path $scratch 'payload'
  19. New-Item -ItemType Directory -Path $payload | Out-Null
  20. [System.IO.File]::WriteAllText((Join-Path $payload 'asset.txt'), 'new asset')
  21. $deepPayload = Join-Path $payload (('nested-' * 20) + '\asset-' + ('x' * 60) + '.txt')
  22. [IO.Directory]::CreateDirectory((Split-Path $deepPayload -Parent)) | Out-Null
  23. [IO.File]::WriteAllText($deepPayload, 'long path asset')
  24. $archive = Join-Path $scratch 'payload.7z'
  25. Invoke-Checked $SevenZip @('a', '-bd', '-t7z', $archive, "$payload/*")
  26. $brokenArchive = Join-Path $scratch 'broken.7z'
  27. [System.IO.File]::WriteAllText($brokenArchive, 'invalid archive')
  28. foreach ($mode in @('first', 'upgrade', 'locked', 'missing-stage', 'broken', 'cancelled')) {
  29. $caseRoot = Join-Path $scratch $mode
  30. $target = Join-Path $caseRoot 'Application'
  31. New-Item -ItemType Directory -Path $caseRoot | Out-Null
  32. if ($mode -ne 'first') {
  33. New-Item -ItemType Directory -Path $target | Out-Null
  34. [System.IO.File]::WriteAllText((Join-Path $target 'asset.txt'), 'old asset')
  35. [System.IO.File]::WriteAllText((Join-Path $target 'obsolete.txt'), 'old only')
  36. $oldDeep = Join-Path $target ([IO.Path]::GetRelativePath($payload, $deepPayload))
  37. [IO.Directory]::CreateDirectory((Split-Path $oldDeep -Parent)) | Out-Null
  38. [IO.File]::WriteAllText($oldDeep, 'old long path asset')
  39. }
  40. $probe = Join-Path $caseRoot 'probe.exe'
  41. $caseArchive = if ($mode -eq 'broken') { $brokenArchive } else { $archive }
  42. $compileArgs = @('/V2', "/DOUTPUT_FILE=$probe", "/DPAYLOAD_FILE=$caseArchive", "/DTARGET_DIR=$target", "/DDSH_SEVENZIP_PATH=$SevenZip")
  43. if ($FrameLibrary) { $compileArgs += "/DSOURCE_DLL=$FrameLibrary" }
  44. if ($mode -eq 'missing-stage') { $compileArgs += '/DMISSING_STAGE' }
  45. if ($mode -eq 'cancelled') { $compileArgs += '/DCANCELLED' }
  46. Invoke-Checked $Makensis ($compileArgs + $fixture)
  47. if ($null -ne $SignExecutable) { & $SignExecutable $probe }
  48. $handle = $null
  49. $process = $null
  50. try {
  51. if ($mode -eq 'locked') {
  52. $handle = [System.IO.File]::Open((Join-Path $target 'asset.txt'), 'Open', 'Read', 'Read')
  53. }
  54. $process = Start-Process -FilePath $probe -ArgumentList '/S' -WindowStyle Hidden -PassThru
  55. if (-not $process.WaitForExit(60000)) {
  56. $process.Kill($true)
  57. $process.WaitForExit()
  58. throw "Directory smoke $mode did not exit within 60 seconds"
  59. }
  60. $expectedExit = if ($mode -in @('first', 'upgrade')) { 0 } else { 2 }
  61. if ($process.ExitCode -ne $expectedExit) {
  62. throw "Directory smoke $mode exited with $($process.ExitCode), expected $expectedExit"
  63. }
  64. } finally {
  65. if ($null -ne $handle) { $handle.Dispose() }
  66. if ($null -ne $process) { $process.Dispose() }
  67. }
  68. $expectedAsset = if ($mode -in @('first', 'upgrade')) { 'new asset' } else { 'old asset' }
  69. if ([System.IO.File]::ReadAllText((Join-Path $target 'asset.txt')) -ne $expectedAsset) {
  70. throw "Directory smoke $mode changed the wrong version"
  71. }
  72. $expectObsolete = $mode -notin @('first', 'upgrade')
  73. if ((Test-Path -LiteralPath (Join-Path $target 'obsolete.txt')) -ne $expectObsolete) {
  74. throw "Directory smoke $mode did not preserve the expected old-only file state"
  75. }
  76. if (@(Get-ChildItem -LiteralPath $caseRoot -Directory | Where-Object Name -ne 'Application').Count -ne 0) {
  77. throw "Directory smoke $mode left transaction directories behind"
  78. }
  79. Write-Output "Directory replacement smoke passed: $mode"
  80. }
  81. } finally {
  82. $tempRoot = [System.IO.Path]::GetFullPath([System.IO.Path]::GetTempPath()).TrimEnd('\') + '\'
  83. $resolvedScratch = [System.IO.Path]::GetFullPath($scratch)
  84. if (-not $resolvedScratch.StartsWith($tempRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
  85. throw "Refusing cleanup outside the temporary root: $resolvedScratch"
  86. }
  87. Remove-Item -LiteralPath $resolvedScratch -Recurse -Force
  88. }