fix(build): build privately and swap generated/ after validation
Build into a per-run sibling directory and replace generated/ only after validation, with a lock around the swap, so a failed or parallel build never leaves generated/ partial. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6d4ac56901
commit
cceb9e4839
5 files changed
+113
-9
No files matched your search
@@ -8,7 +8,7 @@ New-Item $work -ItemType Directory -Force | Out-Null
|
||||
function New-RepoCopy {
|
||||
$copy = Join-Path $work ("case-" + [Guid]::NewGuid())
|
||||
New-Item $copy -ItemType Directory -Force | Out-Null
|
||||
Get-ChildItem $root -Force | Where-Object { $_.Name -notin @('.git','generated') } | ForEach-Object {
|
||||
Get-ChildItem $root -Force | Where-Object { $_.Name -ne '.git' -and $_.Name -ne 'generated' -and $_.Name -notlike 'generated.*' } | ForEach-Object {
|
||||
Copy-Item $_.FullName (Join-Path $copy $_.Name) -Recurse -Force
|
||||
}
|
||||
return $copy
|
||||
@@ -20,12 +20,28 @@ function Test-BuildFails([string]$Label, [scriptblock]$Mutate) {
|
||||
$failed = $false
|
||||
try { & (Join-Path $copy 'scripts/commands/build.ps1') 2>&1 | Out-Null }
|
||||
catch { $failed = $true }
|
||||
$leftovers = @(Get-ChildItem $copy -Force -Filter 'generated*')
|
||||
Remove-Item $copy -Recurse -Force -ErrorAction SilentlyContinue
|
||||
if (-not $failed) { throw "Expected build to fail for case '$Label' but it succeeded." }
|
||||
if ($leftovers.Count) { throw "Failed build for case '$Label' left output behind: $($leftovers.Name -join ', ')" }
|
||||
}
|
||||
|
||||
$baseline = New-RepoCopy
|
||||
& (Join-Path $baseline 'scripts/commands/build.ps1') -Summary | Out-Null
|
||||
$baselineGenerated = Join-Path $baseline 'generated'
|
||||
$expectedFiles = @(Get-ChildItem $baselineGenerated -File -Recurse | ForEach-Object { $_.FullName.Substring($baselineGenerated.Length) } | Sort-Object)
|
||||
$builds = @(1, 2 | ForEach-Object {
|
||||
$build = Start-Process (Get-Process -Id $PID).Path -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ('"' + (Join-Path $baseline 'scripts/commands/build.ps1') + '"'), '-Summary') -NoNewWindow -PassThru -RedirectStandardOutput (Join-Path $work "parallel-$_.out") -RedirectStandardError (Join-Path $work "parallel-$_.err")
|
||||
$null = $build.Handle # Windows PowerShell 5.1 reports ExitCode only when the handle was opened.
|
||||
$build
|
||||
})
|
||||
foreach ($build in $builds) { $build.WaitForExit() }
|
||||
foreach ($index in 0, 1) {
|
||||
if ($builds[$index].ExitCode -ne 0) { throw "Parallel build $($index + 1) failed: $(Get-Content (Join-Path $work "parallel-$($index + 1).err") -Raw)" }
|
||||
}
|
||||
$actualFiles = @(Get-ChildItem $baselineGenerated -File -Recurse | ForEach-Object { $_.FullName.Substring($baselineGenerated.Length) } | Sort-Object)
|
||||
if (Compare-Object $expectedFiles $actualFiles) { throw 'generated/ is incomplete after two parallel builds.' }
|
||||
if (@(Get-ChildItem $baseline -Force | Where-Object Name -like 'generated.*').Count) { throw 'Parallel builds left a build directory, retired tree, or lock behind.' }
|
||||
|
||||
Test-BuildFails 'invalid Claude settings.json' {
|
||||
param($copy)
|
||||
@@ -64,5 +80,5 @@ Test-BuildFails 'unknown placeholder beside an allowed installer placeholder' {
|
||||
}
|
||||
|
||||
Remove-Item $work -Recurse -Force -ErrorAction SilentlyContinue
|
||||
if ($Summary) { Write-Output 'Tests: PASS | build validation' } else { Write-Output 'PASS build validation: invalid JSON, duplicate agent, unknown Claude tool name, duplicate plugin, and leftover placeholders are all rejected' }
|
||||
if ($Summary) { Write-Output 'Tests: PASS | build validation' } else { Write-Output 'PASS build validation: invalid JSON, duplicate agent, unknown Claude tool name, duplicate plugin, and leftover placeholders are all rejected without leaving output; parallel builds swap generated/ safely' }
|
||||
exit 0
|
||||
@@ -16,7 +16,7 @@ trap cleanup EXIT
|
||||
new_repo_copy() {
|
||||
local copy
|
||||
copy=$(mktemp -d "$work/case-XXXXXX")
|
||||
(cd "$root" && tar -cf - --exclude='.git' --exclude='generated' .) | (cd "$copy" && tar -xf -)
|
||||
(cd "$root" && tar -cf - --exclude='.git' --exclude='generated' --exclude='generated.*' .) | (cd "$copy" && tar -xf -)
|
||||
printf '%s' "$copy"
|
||||
}
|
||||
|
||||
@@ -28,11 +28,22 @@ test_build_fails() {
|
||||
printf 'Expected build to fail for case "%s" but it succeeded.\n' "$label" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$(find "$copy" -maxdepth 1 -name 'generated*' -print -quit)" ]; then
|
||||
printf 'Failed build for case "%s" left output behind.\n' "$label" >&2
|
||||
exit 1
|
||||
fi
|
||||
rm -rf -- "$copy"
|
||||
}
|
||||
|
||||
baseline=$(new_repo_copy)
|
||||
bash "$baseline/scripts/commands/build.sh" --summary >/dev/null
|
||||
expected_files=$(cd "$baseline/generated" && find . -type f | sort)
|
||||
bash "$baseline/scripts/commands/build.sh" --summary > "$work/parallel-1.out" 2>&1 & first_build=$!
|
||||
bash "$baseline/scripts/commands/build.sh" --summary > "$work/parallel-2.out" 2>&1 & second_build=$!
|
||||
wait "$first_build" || { printf 'Parallel build 1 failed:\n%s\n' "$(cat "$work/parallel-1.out")" >&2; exit 1; }
|
||||
wait "$second_build" || { printf 'Parallel build 2 failed:\n%s\n' "$(cat "$work/parallel-2.out")" >&2; exit 1; }
|
||||
[ "$(cd "$baseline/generated" && find . -type f | sort)" = "$expected_files" ] || { printf 'generated/ is incomplete after two parallel builds.\n' >&2; exit 1; }
|
||||
[ -z "$(find "$baseline" -maxdepth 1 -name 'generated.*' -print -quit)" ] || { printf 'Parallel builds left a build directory, retired tree, or lock behind.\n' >&2; exit 1; }
|
||||
|
||||
mutate_invalid_json() {
|
||||
printf '} this is not json {' >> "$1/adapters/claude/config/settings.json"
|
||||
@@ -65,4 +76,4 @@ mutate_mixed_placeholders() {
|
||||
}
|
||||
test_build_fails 'unknown placeholder beside an allowed installer placeholder' mutate_mixed_placeholders
|
||||
|
||||
if [ "$summary" = true ]; then printf 'Tests: PASS | build validation\n'; else printf 'PASS build validation: invalid JSON, duplicate agent, unknown Claude tool name, duplicate plugin, and leftover placeholders are all rejected\n'; fi
|
||||
if [ "$summary" = true ]; then printf 'Tests: PASS | build validation\n'; else printf 'PASS build validation: invalid JSON, duplicate agent, unknown Claude tool name, duplicate plugin, and leftover placeholders are all rejected without leaving output; parallel builds swap generated/ safely\n'; fi
|
||||
Reference in new issue
Block a user