param([string]$BashPath='bash') $ErrorActionPreference='Stop' $repoRoot=Split-Path $PSScriptRoot -Parent $psSource=[IO.File]::ReadAllText((Join-Path $repoRoot 'scripts/deploy-from-workstation.ps1')) $parseErrors=$null; $tokens=$null $ast=[Management.Automation.Language.Parser]::ParseInput($psSource,[ref]$tokens,[ref]$parseErrors) if($parseErrors.Count){throw 'Deployment PowerShell syntax invalid'} $function=$ast.Find({param($node) $node -is [Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq 'Test-CaiwuReleaseIncludesCommit'},$true) if(-not $function){throw 'PowerShell inclusion guard missing'} . ([ScriptBlock]::Create($function.Extent.Text)) $bashSource=[IO.File]::ReadAllText((Join-Path $repoRoot 'scripts/deploy-from-mac.sh')).Replace("`r`n","`n") $bashFunction=[regex]::Match($bashSource,'(?ms)^release_includes_commit\(\) \{\n.*?^\}').Value if(-not $bashFunction){throw 'Bash inclusion guard missing'} $fixture=Join-Path ([IO.Path]::GetTempPath()) ('caiwu-release-inclusion-'+[guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $fixture | Out-Null $expectedFixture=[IO.Path]::GetFullPath($fixture) function Invoke-FixtureGit { param([Parameter(ValueFromRemainingArguments=$true)][string[]]$GitArgs) $output=@(git @GitArgs 2>&1) if($LASTEXITCODE -ne 0){throw "Fixture Git failed: $($GitArgs[0])"} return $output } function New-FixtureCommit { param([string]$Value,[string]$Message) [IO.File]::WriteAllText((Join-Path $fixture 'value.txt'),$Value) Invoke-FixtureGit add value.txt | Out-Null Invoke-FixtureGit commit -m $Message | Out-Null return (Invoke-FixtureGit rev-parse HEAD) -join '' } Push-Location $fixture try { Invoke-FixtureGit init -b main | Out-Null Invoke-FixtureGit config user.name 'Release guard fixture' | Out-Null Invoke-FixtureGit config user.email 'release-guard@example.invalid' | Out-Null $base=New-FixtureCommit 'base' 'base' Invoke-FixtureGit switch -c hotfix | Out-Null $hotfix=New-FixtureCommit 'hotfix content' 'hotfix' Invoke-FixtureGit switch main | Out-Null $squashed=New-FixtureCommit 'hotfix content' 'squashed identity' $release=New-FixtureCommit 'next authorized change' 'new release' Invoke-FixtureGit switch -c unrelated $base | Out-Null $unrelated=New-FixtureCommit 'different hotfix content' 'hotfix' $cases=@( @{name='direct ancestor';previous=$base;release=$release;expected=$true}, @{name='same commit';previous=$hotfix;release=$hotfix;expected=$true}, @{name='identical squash tree at head';previous=$hotfix;release=$squashed;expected=$true}, @{name='identical squash tree in release ancestry';previous=$hotfix;release=$release;expected=$true}, @{name='real unmerged change';previous=$unrelated;release=$release;expected=$false}, @{name='rollback to before hotfix';previous=$hotfix;release=$base;expected=$false}, @{name='missing deployed commit';previous=('0'*40);release=$release;expected=$false}, @{name='missing release commit';previous=$hotfix;release=('0'*40);expected=$false} ) foreach($case in $cases){ $actual=Test-CaiwuReleaseIncludesCommit -PreviousSha $case.previous -ReleaseSha $case.release if($actual -ne $case.expected){throw "PowerShell failed: $($case.name)"} $code=$bashFunction+"`nrelease_includes_commit "+'"$1" "$2"' & $BashPath --noprofile --norc -c $code guard $case.previous $case.release $bashActual=$LASTEXITCODE -eq 0 if($bashActual -ne $case.expected){throw "Bash failed: $($case.name)"} } "PASS: $($cases.Count) real Git cases for both PowerShell and Bash guards" } finally { Pop-Location $resolved=(Resolve-Path -LiteralPath $fixture).Path if([IO.Path]::GetFullPath($resolved) -ne $expectedFixture -or (Get-Item -LiteralPath $resolved).Attributes.HasFlag([IO.FileAttributes]::ReparsePoint)){ throw 'Refusing cleanup outside the exact generated fixture directory' } Remove-Item -LiteralPath $resolved -Recurse -Force } exit 0