test/perf/New-PerfFixture.ps1
|
#requires -version 7.0 <# .SYNOPSIS Prepares map fixtures for the performance suite. .DESCRIPTION workspace-* fixtures model a real workspace: a root .build.map.ps1 that #includes one map per project, each with a mix of entry kinds (params, nested groups up to 4 levels, steps, options/_template, npm). flat-* is a single map of N trivial entries, kept for continuity with the old harness. These are stored in test/perf/fixtures and copied to -Path, so every run measures the same maps. -Generate rewrites the stored copies from this script. samples is never stored: it #includes every git-tracked samples/* build map, so it grows with each new sample and always covers current features. .EXAMPLE ./New-PerfFixture.ps1 -Path $env:TEMP/perf -Name workspace-M .EXAMPLE ./New-PerfFixture.ps1 -Generate # regenerate test/perf/fixtures, then commit #> [CmdletBinding()] param( [string]$Path, [ValidateSet('workspace-M', 'workspace-XL', 'flat-1k', 'samples')] [string[]]$Name = @('workspace-M', 'workspace-XL', 'flat-1k', 'samples'), [switch]$Generate, [string]$SamplesPath = (Join-Path $PSScriptRoot '..\..\samples') ) $ErrorActionPreference = 'Stop' $storedPath = Join-Path $PSScriptRoot 'fixtures' if (!$Path) { if (!$Generate) { throw '-Path is required unless -Generate is used.' } $Path = $storedPath } $fixtureShapes = @{ 'workspace-M' = @{ Projects = 5; TasksPerProject = 4 } 'workspace-XL' = @{ Projects = 40; TasksPerProject = 9 } } # Stored fixtures are git-tracked, so format them like the repo-root `qbuild lint -Fix` does; # otherwise -Generate and lint keep rewriting each other's output. function Set-FormattedMap { param([string]$Path, [string]$Content) if (!$script:formatSettings) { Import-Module PSScriptAnalyzer $allSettings = Import-PowerShellDataFile (Join-Path $PSScriptRoot '..\..\..\PSScriptAnalyzerSettings.psd1') $formatRules = @('PSUseConsistentIndentation', 'PSUseConsistentWhitespace', 'PSAlignAssignmentStatement') $script:formatSettings = @{ IncludeRules = $formatRules; Rules = @{} } foreach ($rule in $formatRules) { $script:formatSettings.Rules[$rule] = $allSettings.Rules[$rule] } } Set-Content -Path $Path -NoNewline -Value (Invoke-Formatter -ScriptDefinition ($Content -replace "`r`n", "`n") -Settings $script:formatSettings) } function New-ProjectMap { param([string]$Directory, [string]$Project, [int]$Tasks) New-Item -ItemType Directory -Path $Directory -Force | Out-Null $scripts = [ordered]@{} foreach ($n in 1..15) { $scripts["script$n"] = "echo $Project-$n" } @{ name = $Project; scripts = $scripts } | ConvertTo-Json | Set-Content (Join-Path $Directory 'package.json') "API_URL=http://localhost`nAPI_KEY=dev" | Set-Content (Join-Path $Directory '.env') $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine(@' [ordered]@{ "build" = { param( [ValidateSet('Debug', 'Release')] [string]$Configuration = 'Debug', [switch]$NoRestore, [string]$Framework ) Write-Host "build $Configuration" } "test" = @{ exec = { param([switch]$Watch, [string]$Filter) Write-Host "test" } description = "Run tests" } "tools" = [ordered]@{ "db" = [ordered]@{ "migrate" = @{ exec = { param([ValidateSet('up', 'down')][string]$Direction = 'up') Write-Host "migrate" } description = "Apply database migrations" } "seed" = { Write-Host "seed" } } "fmt" = { param([switch]$Check) Write-Host "fmt" } } "deploy" = [ordered]@{ _settings = @{ process = "steps" } description = "Multi-step deploy" "package" = @{ exec = { Write-Host "package" }; check = { $false } } "upload" = { param([ValidateSet('dev', 'prod')][string]$Environment = 'dev') Write-Host "upload" } "verify" = { Write-Host "verify" } } "env" = [ordered]@{ _settings = @{ process = "options" } _template = "dotenv:.env" description = "API settings in .env" options = [ordered]@{ local = [ordered]@{ "API_URL" = "http://localhost"; "API_KEY" = "dev" } prod = [ordered]@{ "API_URL" = "https://api.example"; "API_KEY" = "prod" } } } "web" = [ordered]@{ _settings = @{ process = "npm" } description = "package.json scripts" } '@) foreach ($n in 1..$Tasks) { [void]$sb.AppendLine(" `"task$n`" = { param([string]`$Name = 'x$n') Write-Host `"task$n`" }") } [void]$sb.AppendLine('}') Set-FormattedMap -Path (Join-Path $Directory '.build.map.ps1') -Content $sb.ToString() } function New-WorkspaceFixture { param([string]$Root, [int]$Projects, [int]$TasksPerProject) New-Item -ItemType Directory -Path $Root -Force | Out-Null $names = foreach ($p in 1..$Projects) { 'p{0:d2}' -f $p } $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine('[ordered]@{') [void]$sb.AppendLine(' "clean" = { Write-Host "clean" }') [void]$sb.AppendLine(' "restore" = @{ exec = { param([switch]$Force) Write-Host "restore" }; description = "Restore everything" }') [void]$sb.AppendLine(' "#include" = [ordered]@{') foreach ($name in $names) { [void]$sb.AppendLine(" `"$name`" = @{ prefix = `$true }") New-ProjectMap -Directory (Join-Path $Root $name) -Project $name -Tasks $TasksPerProject } [void]$sb.AppendLine(' }') [void]$sb.AppendLine('}') Set-FormattedMap -Path (Join-Path $Root '.build.map.ps1') -Content $sb.ToString() } function New-FlatFixture { param([string]$Root, [int]$Count) New-Item -ItemType Directory -Path $Root -Force | Out-Null $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine('@{') foreach ($n in 1..$Count) { [void]$sb.AppendLine(" task$n = { param([string]`$Configuration = `"Debug`") }") } [void]$sb.AppendLine('}') Set-FormattedMap -Path (Join-Path $Root '.build.map.ps1') -Content $sb.ToString() } function New-SamplesFixture { param([string]$Root, [string]$Samples) $Samples = (Resolve-Path $Samples).Path # Tracked files only: local leftovers (obj/, .configmap/, state files) would skew results. $files = @(git -C $Samples ls-files) if ($LASTEXITCODE -ne 0) { throw "git ls-files failed in '$Samples'." } foreach ($file in $files) { $target = Join-Path $Root $file New-Item -ItemType Directory -Path (Split-Path -Parent $target) -Force | Out-Null Copy-Item -LiteralPath (Join-Path $Samples $file) -Destination $target } $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine('[ordered]@{') [void]$sb.AppendLine(' "#include" = [ordered]@{') foreach ($dir in Get-ChildItem $Root -Directory | Sort-Object Name) { if (Test-Path (Join-Path $dir.FullName '.build.map.ps1')) { [void]$sb.AppendLine(" `"$($dir.Name)`" = @{ prefix = `$true }") } } [void]$sb.AppendLine(' }') [void]$sb.AppendLine('}') Set-Content -Path (Join-Path $Root '.build.map.ps1') -Value $sb.ToString() } foreach ($fixture in $Name) { $root = Join-Path $Path $fixture if ($fixture -eq 'samples') { if (!$Generate) { New-SamplesFixture -Root $root -Samples $SamplesPath } continue } if (!$Generate) { $stored = Join-Path $storedPath $fixture if (!(Test-Path $stored)) { throw "Stored fixture '$stored' not found; run with -Generate." } New-Item -ItemType Directory -Path $Path -Force | Out-Null Copy-Item -Path $stored -Destination $Path -Recurse -Force Get-ChildItem $root -Recurse -Directory -Force -Filter '.configmap' | Remove-Item -Recurse -Force continue } if (Test-Path $root) { Remove-Item $root -Recurse -Force } if ($fixture -eq 'flat-1k') { New-FlatFixture -Root $root -Count 1000 } else { $shape = $fixtureShapes[$fixture] New-WorkspaceFixture -Root $root -Projects $shape.Projects -TasksPerProject $shape.TasksPerProject } } |