加快门禁:删除测试窗口、释放桩线程并并行运行三个阶段

关闭后未删除的测试窗口让每次主题切换重排全部残留控件,ui_check 每次关窗还要等桩线程 5 秒。Linux 上结论不变,test_ui_smoke 75→64 s、ui_check 52→16 s;Windows 门禁结果见 .scratch/gate-speed/。
This commit is contained in:
ykcheungab
2026-10-19 09:34:29 +08:00
parent d89535236b
commit a0c13c1dc6
6 changed files with 160 additions and 44 deletions
+105 -18
View File
@@ -3,13 +3,17 @@
Merge gate for orisys-app: lint the change, run both test suites, run the UI check.
.DESCRIPTION
One command that a change cannot skip. It runs, in order:
One command that a change cannot skip. It runs:
1. tools/lint_gate.py the narrow lint rule set over the changed files
2. pytest apps/studio/tests full, with the ``timing`` tests deselected
3. pytest shared/tests full, with the ``timing`` tests deselected
4. apps/studio/tools/ui_check.py the layout / theme / record-channel checks
Lint runs first; 2-4 then run at the same time (they write to separate places), so
the gate takes as long as the slowest of them. -Serial runs 2-4 one after another
with live output; the verdict is the same either way.
and then holds the run to three contracts:
* tools/known_failures.txt must be empty. Any content fails the gate: a known
@@ -44,6 +48,11 @@
.PARAMETER SkipLint
Run only the suites and the UI check (used when diagnosing a red gate).
.PARAMETER Serial
Run the studio suite, the shared suite and ui_check one after another with live
output, instead of at the same time. The verdict is the same; use it to read a
failing stage as it runs, or when a parallel run is suspected of interfering.
.EXAMPLE
powershell -NoProfile -ExecutionPolicy Bypass -File tools/gate.ps1
@@ -58,7 +67,8 @@ param(
[string]$Task = 'gate',
[string]$Python = '',
[string]$LintBase = 'origin/main',
[switch]$SkipLint
[switch]$SkipLint,
[switch]$Serial
)
$ErrorActionPreference = 'Stop'
@@ -218,22 +228,21 @@ try {
}
# -------------------------------------------------------------- test suites
function Invoke-Suite([string]$Name, [string]$Path) {
function Get-SuiteArguments([string]$Name, [string]$Path) {
$safe = $Name.Replace(' ', '-')
$log = Join-Path $Scratch "$safe.log"
$xml = Join-Path $Scratch "$safe.xml"
$base = Join-Path $Scratch "pytest-$safe"
Write-Step "$Name"
$exit = Invoke-Native $Python @(
return @(
'-m', 'pytest', $Path,
'-m', 'not timing',
'-p', 'no:cacheprovider',
"--basetemp=$base",
"--junitxml=$xml"
) $log
"--basetemp=$(Join-Path $Scratch "pytest-$safe")",
"--junitxml=$(Join-Path $Scratch "$safe.xml")"
)
}
function Read-Suite([string]$Name, [int]$Exit) {
$xml = Join-Path $Scratch "$($Name.Replace(' ', '-')).xml"
$counts = [pscustomobject]@{
Tests = 0; Failures = 0; Errors = 0; Skipped = 0; Exit = $exit; Xml = (Test-Path -LiteralPath $xml)
Tests = 0; Failures = 0; Errors = 0; Skipped = 0; Exit = $Exit; Xml = (Test-Path -LiteralPath $xml)
}
if ($counts.Xml) {
try {
@@ -252,6 +261,51 @@ try {
return $counts
}
# A stage started in the background: stdout and stderr go to two files, joined into
# <stage>.log when it ends, so every stage leaves the same one log as a serial run.
function Start-Stage([string]$Name, [string[]]$Arguments) {
$safe = $Name.Replace(' ', '-')
$out = Join-Path $Scratch "$safe.out.tmp"
$err = Join-Path $Scratch "$safe.err.tmp"
# Start-Process joins -ArgumentList with spaces and quotes nothing, so each
# argument is quoted here ('not timing' is one argument).
$quoted = @($Arguments | ForEach-Object { '"' + ($_ -replace '"', '\"') + '"' })
$options = @{
FilePath = $Python
ArgumentList = $quoted
WorkingDirectory = $Repo
RedirectStandardOutput = $out
RedirectStandardError = $err
NoNewWindow = $true
PassThru = $true
}
$began = Get-Date
$process = Start-Process @options
# Windows PowerShell 5.1 reports no ExitCode for a -PassThru process unless its
# handle was read while the process ran.
$null = $process.Handle
return [pscustomobject]@{
Name = $Name; Process = $process; Out = $out; Err = $err; Began = $began
Log = (Join-Path $Scratch "$safe.log")
}
}
function Complete-Stage($Stage) {
$Stage.Process.WaitForExit()
$exit = $Stage.Process.ExitCode
# No exit code is not a pass: treat it like a crash that left no answer.
if ($null -eq $exit) { $exit = -1 }
$text = New-Object System.Collections.ArrayList
foreach ($part in @($Stage.Out, $Stage.Err)) {
if (Test-Path -LiteralPath $part) {
foreach ($line in (Get-Content -LiteralPath $part -Encoding UTF8)) { [void]$text.Add($line) }
Remove-Item -LiteralPath $part -Force -ErrorAction SilentlyContinue
}
}
Set-Content -LiteralPath $Stage.Log -Value $text -Encoding UTF8
return $exit
}
#: An access violation while Qt tears the window down after the last assertion has
#: been seen (exit 0xC0000005 as a signed int). It is allowed and reported; every
#: other non-zero exit from a suite with a clean JUnit report is a gate failure.
@@ -311,18 +365,48 @@ try {
exit 2
}
Write-Step 'studio suite'
$studio = Invoke-Suite 'studio suite' 'apps/studio/tests'
# -------------------------------------------------------------- suites and ui_check
# The three stages are independent processes and write to separate places (studio
# tests: apps/studio/outputs/_test_record*, shared tests: shared/tests/_scratch,
# ui_check: apps/studio/outputs/_*_check, each pytest run its own --basetemp), so by
# default they run at the same time and the gate takes as long as the slowest one.
# Child output is UTF-8: a finding with Chinese text must not crash a cp1252 console.
$env:PYTHONIOENCODING = 'utf-8'
$stages = @(
[pscustomobject]@{ Name = 'studio suite'; Args = (Get-SuiteArguments 'studio suite' 'apps/studio/tests') },
[pscustomobject]@{ Name = 'shared suite'; Args = (Get-SuiteArguments 'shared suite' 'shared/tests') },
[pscustomobject]@{ Name = 'ui_check'; Args = @((Join-Path $Repo 'apps\studio\tools\ui_check.py')) }
)
$exits = @{}
$durations = @{}
if ($Serial) {
foreach ($stage in $stages) {
Write-Step $stage.Name
$log = Join-Path $Scratch "$($stage.Name.Replace(' ', '-')).log"
$began = Get-Date
$exits[$stage.Name] = Invoke-Native $Python $stage.Args $log
$durations[$stage.Name] = ((Get-Date) - $began).TotalSeconds
}
} else {
Write-Step 'studio suite, shared suite and ui_check, in parallel (-Serial for live output)'
$running = @(foreach ($stage in $stages) { Start-Stage $stage.Name $stage.Args })
foreach ($stage in $running) {
$exits[$stage.Name] = Complete-Stage $stage
# StartTime cannot be read once a process has exited; the launch time is ours.
$durations[$stage.Name] = ($stage.Process.ExitTime - $stage.Began).TotalSeconds
Write-Host (" {0,-13} exit {1} {2:n0} s ({3})" -f $stage.Name, $exits[$stage.Name], $durations[$stage.Name], $stage.Log)
}
}
$studio = Read-Suite 'studio suite' $exits['studio suite']
Test-Suite 'studio suite' $studio $budget 'studio_min_tests' 'studio_max_skips'
Write-Step 'shared suite'
$shared = Invoke-Suite 'shared suite' 'shared/tests'
$shared = Read-Suite 'shared suite' $exits['shared suite']
Test-Suite 'shared suite' $shared $budget 'shared_min_tests' 'shared_max_skips'
# -------------------------------------------------------------- ui_check
Write-Step 'ui_check'
$uiLog = Join-Path $Scratch 'ui_check.log'
$uiExit = Invoke-Native $Python @((Join-Path $Repo 'apps\studio\tools\ui_check.py')) $uiLog
$uiExit = $exits['ui_check']
# ui_check prints "scenes: 4 checks: 631 failures: 2"; the last number is the
# verdict. Its exit code is also the verdict: a script that dies partway through
# (missing worker signature, an import error) must not pass on a stale summary line.
@@ -358,6 +442,9 @@ try {
[void]$lines.Add("Platform : Windows")
[void]$lines.Add("Python : $Python")
[void]$lines.Add("LintBase : $LintBase")
$mode = if ($Serial) { 'serial' } else { 'parallel' }
$timing = ($stages | ForEach-Object { "{0} {1:n0} s" -f $_.Name, $durations[$_.Name] }) -join ', '
[void]$lines.Add("Stages : $mode; $timing")
[void]$lines.Add("Scratch : $Scratch")
[void]$lines.Add('')
[void]$lines.Add('| Check | Result | Detail |')