[CmdletBinding()] param( [ValidateSet('Compile', 'Focused', 'Module')][string]$Mode = 'Module', [string]$Modules, [string]$Tests, [switch]$DryRun, [string]$MavenCommand = 'mvn' ) $ErrorActionPreference = 'Stop' $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path $pom = Join-Path $repoRoot 'pom.xml' # 显式选择当前聚合模块,避免漏传参数时意外触发全仓测试。 function Read-Selection([string]$Value, [string]$Name) { if ([string]::IsNullOrWhiteSpace($Value)) { throw "$Name is required." } $items = @($Value.Split(',') | ForEach-Object { $_.Trim() }) if ($items -contains '') { throw "$Name contains an empty selection." } return @($items | Select-Object -Unique) } try { [xml]$rootPom = Get-Content -LiteralPath $pom -Raw $selectedModules = @(Read-Selection $Modules 'Modules') foreach ($module in $selectedModules) { if ($module -cnotin @($rootPom.project.modules.module)) { throw "Unknown active module: $module" } } $selectedTests = @() if ($Mode -eq 'Focused') { $requestedTests = @(Read-Selection $Tests 'Tests') # 只接受明确类名,不接受通配符或方法筛选;每个请求必须唯一命中所选模块。 foreach ($requested in $requestedTests) { if ($requested -cnotmatch '^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$') { throw "Tests must contain exact Java class names: $requested" } $matches = @(foreach ($module in $selectedModules) { $testRoot = Join-Path $repoRoot "$module/src/test/java" if (-not (Test-Path -LiteralPath $testRoot)) { continue } foreach ($file in Get-ChildItem -LiteralPath $testRoot -Recurse -File -Filter "$($requested.Split('.')[-1]).java") { $source = Get-Content -LiteralPath $file.FullName -Raw $package = [regex]::Match($source, '(?m)^\s*package\s+([\w.]+)\s*;').Groups[1].Value $className = if ($package) { "$package.$($file.BaseName)" } else { $file.BaseName } if ($requested -ceq $file.BaseName -or $requested -ceq $className) { [pscustomobject]@{ module = $module; className = $className } } } }) if ($matches.Count -ne 1) { throw "Test must match exactly one source in selected modules: $requested (matches: $($matches.Count))" } $selectedTests += $matches[0] } } elseif ($Tests) { throw 'Tests is only supported in Focused mode.' } } catch { Write-Host "INVALID CHECK: $($_.Exception.Message)" exit 2 } $mavenArgs = @('-T', '1', '-DforkCount=1', '-DreuseForks=true', '-f', $pom, '-pl', ($selectedModules -join ','), '-am') if ($Mode -eq 'Compile') { $mavenArgs += 'test-compile' } else { $mavenArgs += 'test' } if ($Mode -eq 'Focused') { # 上游依赖可能没有所选类,允许该模块零命中;执行后逐类核对本轮报告。 $mavenArgs += "-Dtest=$((@($selectedTests.className | Select-Object -Unique)) -join ',')" $mavenArgs += '-Dsurefire.failIfNoSpecifiedTests=false' } Write-Host "Backend check: $Mode; selected modules: $($selectedModules -join ','); upstream dependencies included" Write-Host 'Downstream consumers are NOT auto-selected; include affected consumers explicitly.' Write-Host ("Command: {0} {1}" -f $MavenCommand, ($mavenArgs -join ' ')) if ($DryRun) { Write-Host 'DRY-RUN PASS: selection validated; no tests or compilation executed.' exit 0 } # 整批占用现有重型名额;已受保护的批次直接执行,不嵌套申请锁。 if (-not $env:CHONGCHONG_LOCAL_CHECK_OWNER) { $childArgs = @('pwsh', '-NoProfile', '-File', $PSCommandPath, '-Mode', $Mode, '-Modules', ($selectedModules -join ','), '-MavenCommand', $MavenCommand) if ($Mode -eq 'Focused') { $childArgs += @('-Tests', $Tests) } & (Join-Path $PSScriptRoot 'local-check.ps1') -WorkingDirectory $repoRoot -Label "Backend $Mode check" -Command $childArgs exit $LASTEXITCODE } $startedAt = [DateTime]::UtcNow Push-Location $repoRoot try { & $MavenCommand @mavenArgs $code = $LASTEXITCODE if ($code -ne 0) { exit $code } if ($Mode -eq 'Focused') { foreach ($selection in $selectedTests) { $reportDir = Join-Path $repoRoot "$($selection.module)/target/surefire-reports" $executed = 0 if (Test-Path -LiteralPath $reportDir) { foreach ($report in Get-ChildItem -LiteralPath $reportDir -File -Filter 'TEST-*.xml') { if ($report.LastWriteTimeUtc -lt $startedAt) { continue } [xml]$xml = Get-Content -LiteralPath $report.FullName -Raw $suite = $xml.testsuite if ($suite.name -ceq $selection.className -or ([string]$suite.name).StartsWith($selection.className + '$', [StringComparison]::Ordinal)) { if ([int]$suite.failures -gt 0 -or [int]$suite.errors -gt 0) { throw "Failed test report: $($suite.name)" } $executed += [int]$suite.tests - [int]$suite.skipped } } } if ($executed -le 0) { throw "No executed tests in fresh reports for $($selection.className). Missing, stale or all-skipped reports cannot pass." } Write-Host "Verified $($selection.className): $executed executed tests" } } } finally { Pop-Location } Write-Host "Backend $Mode check PASS (not a full regression or HTTP acceptance)" exit 0