Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

230 rindas
9.5KB

  1. # E08-S01: automated x128 regression harness.
  2. #
  3. # Runs each suite from a manifest in its own child PowerShell, with a fresh
  4. # build directory (so every suite creates its own D81), a wall-clock limit that
  5. # also bounds emulated cycles, a captured log, and a machine-readable results
  6. # file. When a suite declares artifacts, their SHA-256 values are compared with
  7. # a reviewed baseline; -UpdateBaseline rewrites that baseline.
  8. [CmdletBinding()]
  9. param(
  10. [string]$Manifest = "",
  11. [string]$BuildRoot = "",
  12. [string]$Baseline = "",
  13. [Alias("Results")][string]$ResultsPath = "",
  14. [string[]]$Filter = @(),
  15. [switch]$UpdateBaseline,
  16. [switch]$NoFresh
  17. )
  18. $ErrorActionPreference = "Stop"
  19. $project = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
  20. if ($Manifest -eq "") { $Manifest = Join-Path $PSScriptRoot "suites.ps1" }
  21. if ($BuildRoot -eq "") { $BuildRoot = Join-Path $project "build" }
  22. if ($Baseline -eq "") { $Baseline = Join-Path $PSScriptRoot "baseline.json" }
  23. if ($ResultsPath -eq "") { $ResultsPath = Join-Path $BuildRoot "regression\results.json" }
  24. foreach ($path in @($Manifest)) {
  25. if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "manifest not found: $path" }
  26. }
  27. $suites = @(& $Manifest)
  28. if ($suites.Count -eq 0) { throw "manifest lists no suites: $Manifest" }
  29. # -File invocation passes "source,lexer" as one string, so accept both forms.
  30. $wanted = @()
  31. foreach ($item in $Filter) { $wanted += @($item -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) }
  32. if ($wanted.Count -gt 0) {
  33. $suites = @($suites | Where-Object { $wanted -contains $_.Name })
  34. if ($suites.Count -eq 0) { throw "filter matched no suite: $($wanted -join ', ')" }
  35. }
  36. $logDirectory = Join-Path $BuildRoot "regression"
  37. New-Item -ItemType Directory -Force -Path $logDirectory | Out-Null
  38. $baselineData = @{}
  39. if (Test-Path -LiteralPath $Baseline -PathType Leaf) {
  40. $raw = Get-Content -Raw -LiteralPath $Baseline | ConvertFrom-Json
  41. foreach ($property in $raw.PSObject.Properties) {
  42. $entries = @{}
  43. foreach ($artifact in $property.Value.PSObject.Properties) {
  44. $entries[$artifact.Name] = [string]$artifact.Value
  45. }
  46. $baselineData[$property.Name] = $entries
  47. }
  48. }
  49. # The first line that looks like a diagnosis, so a failed suite names the
  50. # fixture and what was observed instead of only an exit code.
  51. function Get-Signature {
  52. param([AllowEmptyString()][string]$Text)
  53. $lines = @($Text -split "`r?`n" | Where-Object { $_.Trim() -ne "" })
  54. if ($lines.Count -eq 0) { return "" }
  55. $hit = $lines | Where-Object { $_ -match "(?i)fail|differs|error|exception|missing|did not|refus" } |
  56. Select-Object -First 1
  57. if (-not $hit) { $hit = $lines | Select-Object -Last 1 }
  58. $hit = $hit.Trim()
  59. if ($hit.Length -gt 240) { $hit = $hit.Substring(0, 240) }
  60. return $hit
  61. }
  62. function Get-ArtifactFiles {
  63. param([string]$Directory, [string[]]$Patterns)
  64. if (-not $Directory -or $Patterns.Count -eq 0) { return @() }
  65. if (-not (Test-Path -LiteralPath $Directory -PathType Container)) { return @() }
  66. $extensions = @($Patterns | ForEach-Object { $_.ToLowerInvariant() })
  67. $files = Get-ChildItem -LiteralPath $Directory -File -Recurse |
  68. Where-Object { $extensions -contains ("*" + $_.Extension.ToLowerInvariant()) }
  69. return @($files | Sort-Object FullName)
  70. }
  71. $results = @()
  72. $startedAt = (Get-Date).ToString("o")
  73. foreach ($suite in $suites) {
  74. $name = [string]$suite.Name
  75. $script = [string]$suite.Script
  76. if (-not [IO.Path]::IsPathRooted($script)) { $script = Join-Path $project $script }
  77. $timeout = if ($suite.ContainsKey("Timeout")) { [int]$suite.Timeout } else { 300 }
  78. $buildDirectory = $null
  79. if ($suite.ContainsKey("BuildDir") -and $suite.BuildDir) {
  80. $buildDirectory = Join-Path $BuildRoot ([string]$suite.BuildDir)
  81. }
  82. $problems = @()
  83. if (-not (Test-Path -LiteralPath $script -PathType Leaf)) {
  84. $problems += ("suite script missing: $script")
  85. }
  86. if ($problems.Count -eq 0 -and $buildDirectory -and -not $NoFresh) {
  87. if (Test-Path -LiteralPath $buildDirectory) {
  88. Remove-Item -LiteralPath $buildDirectory -Recurse -Force
  89. }
  90. }
  91. $log = Join-Path $logDirectory ("$name.log")
  92. $errorLog = Join-Path $logDirectory ("$name.err.log")
  93. foreach ($path in @($log, $errorLog)) {
  94. if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force }
  95. }
  96. $seconds = 0
  97. $exitCode = -1
  98. $timedOut = $false
  99. $text = ""
  100. if ($problems.Count -eq 0) {
  101. $started = Get-Date
  102. $psi = [Diagnostics.ProcessStartInfo]::new()
  103. $psi.FileName = (Get-Command powershell).Source
  104. $psi.Arguments = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", ('"' + $script + '"')) -join ' '
  105. $psi.UseShellExecute = $false
  106. $psi.CreateNoWindow = $true
  107. $psi.RedirectStandardOutput = $true
  108. $psi.RedirectStandardError = $true
  109. $process = [Diagnostics.Process]::Start($psi)
  110. $outTask = $process.StandardOutput.ReadToEndAsync()
  111. $errTask = $process.StandardError.ReadToEndAsync()
  112. if (-not $process.WaitForExit($timeout * 1000)) {
  113. $timedOut = $true
  114. & taskkill /PID $process.Id /T /F 2>&1 | Out-Null
  115. if (-not $process.WaitForExit(10000)) { $process.Kill() }
  116. }
  117. $seconds = [Math]::Round(((Get-Date) - $started).TotalSeconds, 1)
  118. $exitCode = if ($timedOut) { -1 } else { $process.ExitCode }
  119. $text = ($outTask.Result + "`n" + $errTask.Result)
  120. [IO.File]::WriteAllText($log, $text)
  121. if ($timedOut) {
  122. $problems += ("timed out after $timeout s (cycle budget exhausted)")
  123. } elseif ($exitCode -ne 0) {
  124. $problems += ("exit code $exitCode")
  125. }
  126. if (-not $timedOut -and $suite.ContainsKey("Success") -and $suite.Success -and
  127. $text -notmatch [string]$suite.Success) {
  128. $problems += ("missing success marker '$($suite.Success)'")
  129. }
  130. }
  131. # Artifact capture and comparison.
  132. $artifacts = @()
  133. $patterns = @()
  134. if ($suite.ContainsKey("Artifacts")) { $patterns = @($suite.Artifacts) }
  135. if ($buildDirectory -and $patterns.Count -gt 0) {
  136. $expected = if ($baselineData.ContainsKey($name)) { $baselineData[$name] } else { @{} }
  137. foreach ($file in (Get-ArtifactFiles -Directory $buildDirectory -Patterns $patterns)) {
  138. $relative = $file.FullName.Substring($buildDirectory.Length).TrimStart('\', '/')
  139. $hash = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash
  140. $state = if (-not $expected.ContainsKey($relative)) {
  141. "new"
  142. } elseif ($expected[$relative] -ne $hash) {
  143. "changed"
  144. } else {
  145. "match"
  146. }
  147. if (-not $UpdateBaseline -and $state -ne "match" -and $baselineData.ContainsKey($name)) {
  148. $problems += ("artifact ${relative}: $state")
  149. }
  150. $artifacts += ([ordered]@{
  151. path = $relative; sha256 = $hash; status = $state
  152. })
  153. }
  154. foreach ($relative in $expected.Keys) {
  155. if (-not ($artifacts | Where-Object { $_.path -eq $relative })) {
  156. if (-not $UpdateBaseline) { $problems += ("artifact ${relative}: missing") }
  157. $artifacts += ([ordered]@{ path = $relative; sha256 = ""; status = "missing" })
  158. }
  159. }
  160. }
  161. $status = if ($problems.Count -eq 0) { "pass" } else { "fail" }
  162. # A bare exit status says nothing about the fixture; prefer the diagnosis
  163. # the suite printed, and fall back to the structural problem when it
  164. # produced no output at all.
  165. $observed = Get-Signature $text
  166. $signature = if ($problems.Count -eq 0) { $observed }
  167. elseif ($problems[0] -match "^exit code" -and $observed) { $observed }
  168. else { $problems[0] }
  169. $results += ([ordered]@{
  170. name = $name
  171. status = $status
  172. seconds = $seconds
  173. exitCode = $exitCode
  174. timedOut = $timedOut
  175. signature = $signature
  176. problems = @($problems)
  177. log = $log
  178. artifacts = @($artifacts)
  179. })
  180. Write-Host (" {0,-14} {1,-4} {2,6}s {3}" -f $name, $status, $seconds, $signature)
  181. }
  182. $finishedAt = (Get-Date).ToString("o")
  183. $report = [ordered]@{
  184. startedAt = $startedAt
  185. finishedAt = $finishedAt
  186. manifest = $Manifest
  187. suites = @($results)
  188. passed = @($results | Where-Object { $_.status -eq "pass" }).Count
  189. failed = @($results | Where-Object { $_.status -eq "fail" }).Count
  190. }
  191. $resultsDirectory = Split-Path $ResultsPath -Parent
  192. New-Item -ItemType Directory -Force -Path $resultsDirectory | Out-Null
  193. $report | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $ResultsPath
  194. if ($UpdateBaseline) {
  195. $fresh = [ordered]@{}
  196. foreach ($entry in $results) {
  197. if ($entry.status -ne "pass" -or $entry.artifacts.Count -eq 0) { continue }
  198. $map = [ordered]@{}
  199. foreach ($artifact in $entry.artifacts) {
  200. $map[$artifact.path] = $artifact.sha256
  201. }
  202. $fresh[$entry.name] = $map
  203. }
  204. $fresh | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $Baseline
  205. Write-Host ("baseline written: {0} ({1} suites)" -f $Baseline, $fresh.Count)
  206. }
  207. Write-Host ("regression: {0} passed, {1} failed; results {2}" -f $report.passed, $report.failed, $ResultsPath)
  208. if ($report.failed -gt 0) {
  209. foreach ($entry in ($results | Where-Object { $_.status -eq "fail" })) {
  210. Write-Host (" {0}: {1}" -f $entry.name, ($entry.problems -join "; "))
  211. }
  212. exit 1
  213. }

Powered by TurnKey Linux.