diff --git a/Bun/.env b/Bun/.env.example similarity index 100% rename from Bun/.env rename to Bun/.env.example diff --git a/Bun/test.bat b/Bun/test.bat index 8ceaa99..b856566 100644 --- a/Bun/test.bat +++ b/Bun/test.bat @@ -1,59 +1,182 @@ @echo off setlocal +set "TEST_BAT_PATH=%~f0" +powershell -NoProfile -ExecutionPolicy Bypass -Command "$path=$env:TEST_BAT_PATH; $raw=Get-Content -Raw -LiteralPath $path; $marker='# POWERSHELL'; $start=$raw.LastIndexOf($marker); if($start -lt 0){ throw 'PowerShell marker not found' }; $ps=$raw.Substring($start + $marker.Length); Set-Location -LiteralPath (Split-Path -Parent $path); Invoke-Expression $ps" +exit /b %ERRORLEVEL% -REM FORCE_BUILD=1: rebuild the Bun image with --no-cache before running tests. -REM FORCE_BUILD=0: reuse the existing Bun image; build only with Docker cache. -set "FORCE_BUILD=0" +# POWERSHELL +$ErrorActionPreference = 'Stop' +if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { + $PSNativeCommandUseErrorActionPreference = $false +} -REM VERBOSE=1: print full stdout/stderr logs for every test. -REM VERBOSE=0: print only [PASS], [NO], and reason lines; full logs stay in logs\. -set "VERBOSE=0" +$forceBuild = if ($env:FORCE_BUILD) { $env:FORCE_BUILD } else { '0' } +$verbose = if ($env:VERBOSE) { $env:VERBOSE } else { '0' } +$composeBase = @('compose', '--env-file', '.env', '-f', 'compose.yml') -cd /d "%~dp0" +function Write-TaggedLine { + param([string] $Line) -if not exist logs mkdir logs + if ($Line.StartsWith('[PASS]')) { + Write-Host $Line -ForegroundColor Green + } elseif ($Line.StartsWith('[NO]') -or $Line.StartsWith('reason:')) { + Write-Host $Line -ForegroundColor Red + } elseif ($Line.StartsWith('[RUN]')) { + Write-Host $Line -ForegroundColor Cyan + } elseif ($Line.StartsWith('[INFO]')) { + Write-Host $Line -ForegroundColor DarkCyan + } +} -if "%FORCE_BUILD%"=="1" ( - docker compose --env-file .env -f compose.yml build --no-cache >logs\build.out 2>logs\build.err - if errorlevel 1 ( - call :build_failed - exit /b %errorlevel% - ) -) else ( - docker compose --env-file .env -f compose.yml build >logs\build.out 2>logs\build.err - if errorlevel 1 ( - call :build_failed - exit /b %errorlevel% - ) +function Invoke-Docker { + param([string[]] $Arguments) + + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + try { + $output = & docker @Arguments 2>&1 + $code = $LASTEXITCODE + } finally { + $ErrorActionPreference = $previousErrorActionPreference + } + + $lines = @( + $output | ForEach-Object { + if ($_ -is [System.Management.Automation.ErrorRecord]) { + $_.Exception.Message + } else { + $_.ToString() + } + } | Where-Object { + $_ -and $_ -ne 'System.Management.Automation.RemoteException' + } + ) + + [pscustomobject]@{ + Code = $code + Lines = $lines + } +} + +function Print-FilteredOutput { + param([string[]] $Lines) + + foreach ($line in $Lines) { + if ($verbose -eq '1') { + Write-Host $line + } else { + Write-TaggedLine $line + } + } +} + +function Print-FailureDiagnostics { + param([string[]] $Lines) + + $hasNoLine = $Lines | Where-Object { $_.StartsWith('[NO]') } | Select-Object -First 1 + if (-not $hasNoLine) { + Write-Host '--- raw docker output ---' -ForegroundColor Yellow + $Lines | Select-Object -Last 80 | ForEach-Object { Write-Host $_ } + } +} + +function Build-Image { + Write-Host '' + Write-TaggedLine '[RUN] Build Bun image' + + $sourceFiles = Get-ChildItem -File Dockerfile, compose.yml, .env, .env.example + $env:SOURCE_VERSION = ($sourceFiles | Sort-Object LastWriteTimeUtc -Descending | Select-Object -First 1).LastWriteTimeUtc.Ticks + + $dockerArgs = $composeBase + @('build') + if ($forceBuild -eq '1') { + $dockerArgs += '--no-cache' + } + + $result = Invoke-Docker $dockerArgs + if ($result.Code -ne 0) { + Write-TaggedLine '[NO] Build Bun image' + $result.Lines | ForEach-Object { Write-Host $_ } + exit $result.Code + } + + Write-TaggedLine '[PASS] Build Bun image' + if ($verbose -eq '1') { + $result.Lines | ForEach-Object { Write-Host $_ } + } +} + +function Run-Case { + param( + [string] $Title, + [string] $Script + ) + + Write-Host '' + Write-TaggedLine "[RUN] $Title" + + $casePath = Join-Path (Get-Location) ('.test-case-{0}.sh' -f ([guid]::NewGuid().ToString('N'))) + Set-Content -LiteralPath $casePath -Value $Script -NoNewline -Encoding Ascii + + try { + $volumeArg = "${casePath}:/tmp/test-case.sh:ro" + $dockerArgs = $composeBase + @('run', '--rm', '--no-deps', '-T', '--volume', $volumeArg, 'test', 'bash', '/tmp/test-case.sh') + $result = Invoke-Docker $dockerArgs + } finally { + Remove-Item -LiteralPath $casePath -Force -ErrorAction SilentlyContinue + } + + Print-FilteredOutput $result.Lines + if ($result.Code -ne 0) { + Print-FailureDiagnostics $result.Lines + } + + return $result.Code +} + +if (-not (Test-Path -LiteralPath '.env')) { + Write-TaggedLine '[NO] .env exists' + Write-TaggedLine 'reason: create Bun\.env from Bun\.env.example.' + exit 1 +} + +$tests = @( + @{ + Title = 'Bun exists' + Script = @' +set -euo pipefail +bun --version >/tmp/bun-version +echo "[PASS] bun exists ($(cat /tmp/bun-version))" +'@ + } + @{ + Title = 'Bun uses npmmirror registry' + Script = @' +set -euo pipefail +grep -F 'registry.npmmirror.com' /root/.bunfig.toml >/dev/null +tmpdir="$(mktemp -d)" +cd "${tmpdir}" +printf '%s\n' '{"name":"bun-registry-test","version":"0.0.0","private":true,"dependencies":{"is-number":"7.0.0"}}' > package.json +bun install --lockfile-only >/tmp/bun-install.out 2>/tmp/bun-install.err +echo '[PASS] bun install uses npmmirror registry' +'@ + } ) -docker compose --env-file .env -f compose.yml run --rm --no-deps test bun --version >logs\bun-exists.out 2>logs\bun-exists.err -set "BUN_EXISTS_CODE=%errorlevel%" -if "%BUN_EXISTS_CODE%"=="0" ( - call :pass "bun exists" -) else ( - call :no "bun exists" logs\bun-exists.err -) +Build-Image -docker compose --env-file .env -f compose.yml run --rm --no-deps test bash -lc "grep -F registry.npmmirror.com /root/.bunfig.toml && tmpdir=$(mktemp -d) && cd $tmpdir && printf '{\"name\":\"bun-registry-test\",\"version\":\"0.0.0\",\"private\":true,\"dependencies\":{\"is-number\":\"7.0.0\"}}\n' > package.json && bun install --lockfile-only" >logs\bun-install-registry.out 2>logs\bun-install-registry.err -set "BUN_INSTALL_REGISTRY_CODE=%errorlevel%" -if "%BUN_INSTALL_REGISTRY_CODE%"=="0" ( - call :pass "bun install uses npmmirror registry" -) else ( - call :no "bun install uses npmmirror registry" logs\bun-install-registry.err -) +$failed = $false +foreach ($test in $tests) { + $code = Run-Case -Title $test.Title -Script $test.Script + if ($code -ne 0) { + $failed = $true + } +} -if not "%BUN_EXISTS_CODE%"=="0" exit /b %BUN_EXISTS_CODE% -exit /b %BUN_INSTALL_REGISTRY_CODE% +Write-Host '' +if ($failed) { + Write-TaggedLine '[NO] one or more tests failed' + exit 1 +} -:pass -powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host '[PASS] %~1' -ForegroundColor Green" -exit /b 0 - -:no -powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host '[NO] %~1' -ForegroundColor Red; $reason=(Get-Content -LiteralPath '%~2' -ErrorAction SilentlyContinue | Where-Object { $_ } | Select-Object -Last 1); if(!$reason){ $reason='see logs for details' }; Write-Host ('reason: ' + $reason) -ForegroundColor Red" -exit /b 0 - -:build_failed -powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host '[NO] build Bun image' -ForegroundColor Red; $reason=(Get-Content -LiteralPath 'logs\build.err' -ErrorAction SilentlyContinue | Where-Object { $_ } | Select-Object -Last 1); if(!$reason){ $reason='docker compose build failed; see Bun\logs\build.err' }; Write-Host ('reason: ' + $reason) -ForegroundColor Red" -exit /b 0 +Write-TaggedLine '[PASS] all tests passed' +exit 0