Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ jobs:
Start-Sleep -Seconds 10
}
throw 'FAIL the docker daemon never became ready'
# TODO For compiling moby only.
# Because https://github.com/actions/runner-images/blob/win11-arm64/20260920.174/images/windows/Windows11-Arm64-Readme.md is still using `Go 1.24.13`.
- if: matrix.runs-on == 'windows-11-arm'
run: pwsh -NoProfile -File tests/e2e/windows/install-docker.ps1
uses: actions/setup-go@v7
with:
go-version: 1.26.8
cache: false
- if: matrix.runs-on == 'windows-11-arm'
run: pwsh -NoProfile -File tests/e2e/windows/install-docker-arm64.ps1
- run: pwsh -NoProfile -File tests/e2e/windows/e2e.ps1
4 changes: 3 additions & 1 deletion docs/e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@ winget install --id Microsoft.PowerShell --source winget --exact
2. Execute in PowerShell 7,

```powershell
winget install --id Git.Git --source winget --exact
winget install --id GoLang.Go --source winget --exact
git clone git@github.com:version-fox/vfox-flutter.git
cd ./vfox-flutter/
pwsh -NoProfile -File .\tests\e2e\windows\install-docker.ps1
pwsh -NoProfile -File .\tests\e2e\windows\install-docker-arm64.ps1
pwsh -NoProfile -File .\tests\e2e\windows\e2e.ps1
```
5 changes: 4 additions & 1 deletion lib/git.lua
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ function M.resetDir(dir)
end

function M.init(root)
return exec("git init -q " .. quote(root))
if not exec("git init -q " .. quote(root)) then
return false
end
return git(root, "config core.longpaths true")
end

function M.fetch(root, remote, ref)
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/windows/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ FROM ${BASE_IMAGE}

SHELL ["powershell", "-NoProfile", "-Command"]

COPY tests/e2e/windows/lib.ps1 C:\lib.ps1
COPY tests/e2e/windows/bootstrap-tools.ps1 C:\bootstrap-tools.ps1
RUN powershell -NoProfile -ExecutionPolicy Bypass -File 'C:\bootstrap-tools.ps1'

Expand Down
22 changes: 15 additions & 7 deletions tests/e2e/windows/bootstrap-tools.ps1
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
$ErrorActionPreference = 'Stop'
. "$PSScriptRoot\lib.ps1"

$IsArm64 = $env:PROCESSOR_ARCHITECTURE -eq 'ARM64'
$pwshArch = if ($IsArm64) { 'arm64' } else { 'x64' }
$gitAsset = if ($IsArm64) { 'Git-2.55.0.5-arm64.exe' } else { 'Git-2.55.0.5-64-bit.exe' }

$msi = "$env:TEMP\pwsh.msi"
& curl.exe -fsSL -o $msi "https://github.com/PowerShell/PowerShell/releases/download/v7.6.6/PowerShell-7.6.6-win-$pwshArch.msi"
& msiexec.exe /i $msi /quiet /norestart
if ($LASTEXITCODE -notin 0, 3010) { throw "PowerShell MSI install failed (exit $LASTEXITCODE)" }
$pwshInstaller = "$env:TEMP\pwsh.msi"
$pwshUrl = "https://github.com/PowerShell/PowerShell/releases/download/v7.6.6/PowerShell-7.6.6-win-$pwshArch.msi"
$gitInstaller = "$env:TEMP\git-installer.exe"
$gitUrl = "https://github.com/git-for-windows/git/releases/download/v2.55.0.windows.5/$gitAsset"

$git = "$env:TEMP\git-installer.exe"
& curl.exe -fsSL -o $git "https://github.com/git-for-windows/git/releases/download/v2.55.0.windows.5/$gitAsset"
Start-Process $git -Wait -ArgumentList '/VERYSILENT', '/NORESTART', '/SP-', '/SUPPRESSMSGBOXES'
Invoke-Native { & curl.exe -fsSL --retry 3 --retry-delay 5 --retry-all-errors -o $pwshInstaller $pwshUrl } 'the PowerShell download'
Invoke-Native { & curl.exe -fsSL --retry 3 --retry-delay 5 --retry-all-errors -o $gitInstaller $gitUrl } 'the Git download'
$pwshSetup = Start-Process msiexec.exe -Wait -PassThru -ArgumentList '/i', "`"$pwshInstaller`"", '/quiet', '/norestart'
if ($pwshSetup.ExitCode -notin 0, 3010) { throw "FAIL the PowerShell MSI install exited with code $($pwshSetup.ExitCode)" }
$gitSetup = Start-Process $gitInstaller -Wait -PassThru -ArgumentList '/VERYSILENT', '/NORESTART', '/SP-', '/SUPPRESSMSGBOXES'
if ($gitSetup.ExitCode -ne 0) { throw "FAIL the Git installer exited with code $($gitSetup.ExitCode)" }
if (-not (Test-Path 'C:\Program Files\PowerShell\7\pwsh.exe')) { throw 'FAIL pwsh.exe is missing after the PowerShell MSI install' }
if (-not (Test-Path 'C:\Program Files\Git\cmd\git.exe')) { throw 'FAIL git.exe is missing after the Git installer' }
Invoke-Native { & 'C:\Program Files\Git\cmd\git.exe' config --system core.longpaths true } 'enable system-wide git long paths'
New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1 -PropertyType DWORD -Force | Out-Null
85 changes: 64 additions & 21 deletions tests/e2e/windows/e2e.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $false
. "$PSScriptRoot\lib.ps1"

$Repo = (Resolve-Path "$PSScriptRoot\..\..\..").Path

Expand All @@ -13,8 +14,7 @@ $platform = "windows/$arch"
$Image = "vfox-flutter-e2e:windows-$arch"
$baseImage = "mcr.microsoft.com/windows/servercore:ltsc2025-KB5122871-$arch"

& docker build --pull --platform $platform --build-arg "BASE_IMAGE=$baseImage" -f "$PSScriptRoot\Dockerfile" -t $Image $Repo
if ($LASTEXITCODE -ne 0) { throw "FAIL docker build exited with code $LASTEXITCODE" }
Invoke-Native { & docker build --pull --platform $platform --build-arg "BASE_IMAGE=$baseImage" -f "$PSScriptRoot\Dockerfile" -t $Image $Repo } 'docker build'

$foxes = if ($env:VFOX_VERSION) { @($env:VFOX_VERSION) } else { @('latest', 'main') }
$defaultFlavours = @('official', 'ohos')
Expand All @@ -27,30 +27,73 @@ $flavours = if ($env:FLAVOR) { @($env:FLAVOR) } else { $defaultFlavours }
$mirrorList = if ($env:MIRROR) { @($env:MIRROR -split '\s+' | Where-Object { $_ -ne '' }) } else { $defaultMirrors }
$mirrorExplicit = -not [string]::IsNullOrWhiteSpace($env:MIRROR)

$combos = foreach ($vfox in $foxes) {
$combos = [System.Collections.Generic.List[object]]::new()
foreach ($vfox in $foxes) {
foreach ($flavor in $flavours) {
foreach ($mirror in $mirrorList) {
if ((-not $mirrorExplicit) -and ($mirror -ne 'default') -and ($vfox -ne 'latest')) { continue }
[pscustomobject]@{ Vfox = $vfox; Flavor = $flavor; Mirror = $mirror }
$combos.Add([pscustomobject]@{ Vfox = $vfox; Flavor = $flavor; Mirror = $mirror })
}
}
}

foreach ($combo in $combos) {
$vfox = $combo.Vfox
$flavor = $combo.Flavor
$mirror = $combo.Mirror
$prefix = "vfox $vfox, $flavor, mirror $mirror, $platform"
Write-Output "[$prefix] === start ==="
$runArgs = @('run', '-d', '--platform', $platform, '-e', "VFOX_VERSION=$vfox", '-e', "FLAVOR=$flavor")
if ($mirror -ne 'default') { $runArgs += @('-e', "FLUTTER_STORAGE_BASE_URL=$mirror") }
$runArgs += $Image
$cid = & docker @runArgs
if ($LASTEXITCODE -ne 0) { throw ("FAIL docker run ({0}) failed to start" -f $prefix) }
$exitCode = & docker wait $cid
$logs = (& docker logs $cid) -join "`r`n"
Write-Output $logs
& docker rm $cid
if ([int]$exitCode -ne 0) { throw ("FAIL docker run ({0}) exited with code {1}" -f $prefix, $exitCode) }
if ($logs -notmatch '=== vfox') { throw "FAIL docker run ($prefix) produced no test output" }
$maxJobs = 2
if ($env:E2E_MAX_JOBS) { $maxJobs = [int]$env:E2E_MAX_JOBS }
if ($maxJobs -lt 1) { $maxJobs = 1 }

function Get-ComboSlug {
param([object] $Combo)
return ($Combo.Vfox + '-' + $Combo.Flavor + '-' + ($Combo.Mirror -replace '[^A-Za-z0-9]', '_'))
}

function Get-ContainerExitCode {
param([string] $Id)
$code = & docker wait $Id
$code = if ($null -eq $code) { '' } else { ($code | Select-Object -Last 1).Trim() }
if ($code -notmatch '^-?\d+$') { throw "FAIL docker wait $Id reported no exit code" }
return [long]$code
}

$failures = [System.Collections.Generic.List[string]]::new()
for ($start = 0; $start -lt $combos.Count; $start += $maxJobs) {
$end = [math]::Min($start + $maxJobs - 1, $combos.Count - 1)
$batch = [System.Collections.Generic.List[object]]::new()

for ($i = $start; $i -le $end; $i++) {
$combo = $combos[$i]
$vfox = $combo.Vfox
$flavor = $combo.Flavor
$mirror = $combo.Mirror
$prefix = "vfox $vfox, $flavor, mirror $mirror, $platform"
Write-Output "[$prefix] === start ==="

$slug = Get-ComboSlug $combo
$runArgs = @(
'run', '-d', '--platform', $platform,
'-e', "VFOX_VERSION=$vfox", '-e', "FLAVOR=$flavor",
'-e', "VFOX_E2E_SLOT=$slug")
if ($mirror -ne 'default') { $runArgs += @('-e', "FLUTTER_STORAGE_BASE_URL=$mirror") }
$runArgs += $Image

$cid = & docker @runArgs
if ($LASTEXITCODE -ne 0) { throw "FAIL docker run ($prefix) failed to start" }
$batch.Add([pscustomobject]@{ Prefix = $prefix; Id = $cid })
}

foreach ($entry in $batch) {
$prefix = $entry.Prefix
$exitCode = Get-ContainerExitCode $entry.Id
$logs = (& docker logs $entry.Id) -join "`r`n"
Write-Output $logs
& docker rm $entry.Id | Out-Null

if ($exitCode -ne 0) {
$failures.Add("FAIL docker run ($prefix) exited with code $exitCode")
}
elseif ($logs -notmatch '=== vfox') {
$failures.Add("FAIL docker run ($prefix) produced no test output")
}
}
}

if ($failures.Count -gt 0) { throw ($failures -join "`r`n") }
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
. "$PSScriptRoot\lib.ps1"

function Test-Engine {
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { return $false }
Expand Down Expand Up @@ -29,21 +30,12 @@ else {
New-Item -ItemType Directory -Force -Path $root | Out-Null
Write-Output 'Building docker from source for windows/arm64 ...'

$goMsi = "$env:TEMP\go.msi"
& curl.exe -fsSL -o $goMsi 'https://go.dev/dl/go1.27.1.windows-arm64.msi'
if ($LASTEXITCODE -ne 0) { throw "FAIL the Go download exited with code $LASTEXITCODE" }
& msiexec.exe /i $goMsi /quiet /norestart
if ($LASTEXITCODE -notin 0, 3010) { throw "FAIL the Go MSI install exited with code $LASTEXITCODE" }
Remove-Item $goMsi
if (-not (Get-Command go -ErrorAction SilentlyContinue)) { throw 'FAIL go is not on the PATH' }
$env:GOPATH = Join-Path $root 'gopath'
New-Item -ItemType Directory -Force -Path $env:GOPATH | Out-Null
$env:PATH = "C:\Program Files\Go\bin;$env:GOPATH\bin;$env:PATH"

$moby = Join-Path $root 'moby'
git clone --depth 1 --branch "docker-v$mobyVersion" https://github.com/moby/moby.git $moby
if ($LASTEXITCODE -ne 0) { throw "FAIL the moby clone exited with code $LASTEXITCODE" }
go install github.com/tc-hib/go-winres@v0.3.1
if ($LASTEXITCODE -ne 0) { throw "FAIL the go-winres install exited with code $LASTEXITCODE" }
Invoke-Native { git clone --depth 1 --branch "docker-v$mobyVersion" https://github.com/moby/moby.git $moby } 'the moby clone'
Invoke-Native { go install github.com/tc-hib/go-winres@v0.3.1 } 'the go-winres install'
$env:DOCKERCLI_VERSION = $mobyVersion
$env:VERSION = $mobyVersion
Push-Location $moby
Expand Down
19 changes: 15 additions & 4 deletions tests/e2e/windows/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@ param(

$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
. "$PSScriptRoot\lib.ps1"

vfox install flutter@$Version
if ($LASTEXITCODE -ne 0) { throw "FAIL vfox install flutter@$Version exited with code $LASTEXITCODE" }
vfox use --global flutter@$Version
if ($LASTEXITCODE -ne 0) { throw "FAIL vfox use --global flutter@$Version exited with code $LASTEXITCODE" }
$installCode = 1
for ($attempt = 1; $attempt -le 3; $attempt++) {
$PSNativeCommandUseErrorActionPreference = $false
vfox install flutter@$Version
$installCode = $LASTEXITCODE
$PSNativeCommandUseErrorActionPreference = $true
if ($installCode -eq 0) { break }
if ($attempt -lt 3) {
Write-Output "retrying vfox install flutter@$Version, attempt $attempt exited $installCode"
Start-Sleep -Seconds (10 * $attempt)
}
}
if ($installCode -ne 0) { throw "FAIL vfox install flutter@$Version exited with code $installCode" }
Invoke-Native { vfox use --global flutter@$Version } "vfox use --global flutter@$Version"
14 changes: 14 additions & 0 deletions tests/e2e/windows/lib.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function Invoke-Native {
param(
[Parameter(Mandatory)] [scriptblock] $Cmd,
[Parameter(Mandatory)] [string] $What,
[int[]] $Ok = 0
)
$prev = $PSNativeCommandUseErrorActionPreference
$PSNativeCommandUseErrorActionPreference = $false
& $Cmd
$code = $LASTEXITCODE
$PSNativeCommandUseErrorActionPreference = $prev
if ($null -eq $code) { throw "FAIL $What did not run" }
if ($code -notin $Ok) { throw "FAIL $What exited with code $code" }
}
15 changes: 11 additions & 4 deletions tests/e2e/windows/run.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
. "$PSScriptRoot\lib.ps1"

$vfox = if ($env:VFOX_VERSION) { $env:VFOX_VERSION } else { 'latest' }
$flavor = if ($env:FLAVOR) { $env:FLAVOR } else { throw 'FAIL FLAVOR is not set' }
Expand Down Expand Up @@ -43,7 +44,13 @@ if ($flavor -eq 'official') {
if ($bogusOutput -notmatch [regex]::Escape('invalid.example.invalid')) { throw ("FAIL bogus mirror error is missing invalid.example.invalid`n--- actual ---`n{0}" -f $bogusOutput) }
Write-Output 'PASS bogus mirror error contains invalid.example.invalid'
}
& pwsh -File "$PSScriptRoot\install.ps1" -Version $version
if ($LASTEXITCODE -ne 0) { throw "FAIL install.ps1 (flutter $version) exited with code $LASTEXITCODE" }
& pwsh -File "$PSScriptRoot\verify.ps1" -Flavor $flavor
if ($LASTEXITCODE -ne 0) { throw "FAIL verify.ps1 ($flavor) exited with code $LASTEXITCODE" }
Invoke-Native { & pwsh -NoProfile -File "$PSScriptRoot\install.ps1" -Version $version } "install.ps1 (flutter $version)"

$PSNativeCommandUseErrorActionPreference = $false
$activation = @(vfox activate pwsh) -join "`r`n"
$activationCode = $LASTEXITCODE
$PSNativeCommandUseErrorActionPreference = $true
if ($activationCode -ne 0) { throw "FAIL vfox activate pwsh exited with code $activationCode" }
Invoke-Expression $activation

Invoke-Native { & pwsh -NoProfile -File "$PSScriptRoot\verify.ps1" -Flavor $flavor } "verify.ps1 ($flavor)"
47 changes: 27 additions & 20 deletions tests/e2e/windows/setup.ps1
Original file line number Diff line number Diff line change
@@ -1,33 +1,52 @@
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
. "$PSScriptRoot\lib.ps1"

$VfoxVersion = if ($env:VFOX_VERSION) { $env:VFOX_VERSION } else { 'latest' }

$slot = if ($env:VFOX_E2E_SLOT) { $env:VFOX_E2E_SLOT } else { 'default' }
$slotRoot = Join-Path $env:USERPROFILE "vfox-e2e-runs\$slot"
New-Item -ItemType Directory -Force -Path (Join-Path $slotRoot 'tmp') | Out-Null
$env:USERPROFILE = $slotRoot
$env:VFOX_HOME = Join-Path $slotRoot '.vfox'
$env:TEMP = Join-Path $slotRoot 'tmp'
$env:TMP = $env:TEMP

$RepoRoot = (Resolve-Path "$PSScriptRoot\..\..\..").Path
$WorkDir = "$env:TEMP\vfox-flutter-e2e"
New-Item -ItemType Directory -Force -Path $WorkDir | Out-Null
$VfoxExe = "$WorkDir\vfox.exe"
$PluginZip = "$WorkDir\flutter.zip"
$env:PATH = "$WorkDir;$env:PATH"

$CurlRetry = @('--retry', '3', '--retry-delay', '5', '--retry-all-errors')

function Install-VfoxRelease {
$tag = ((& curl.exe -fsSLI -o NUL -w '%{url_effective}' 'https://github.com/version-fox/vfox/releases/latest') -split '/tag/')[-1].Trim()
$PSNativeCommandUseErrorActionPreference = $false
$tag = ((& curl.exe @CurlRetry -fsSLI -o NUL -w '%{url_effective}' 'https://github.com/version-fox/vfox/releases/latest') -split '/tag/')[-1].Trim()
$PSNativeCommandUseErrorActionPreference = $true
$vfoxArch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'aarch64' } else { 'x86_64' }
$zip = "$WorkDir\vfox.zip"
& curl.exe -fsSL -o $zip "https://github.com/version-fox/vfox/releases/download/$tag/vfox_$($tag.TrimStart('v'))_windows_$vfoxArch.zip"
tar -xf $zip -C $WorkDir
Invoke-Native { & curl.exe @CurlRetry -fsSL -o $zip "https://github.com/version-fox/vfox/releases/download/$tag/vfox_$($tag.TrimStart('v'))_windows_$vfoxArch.zip" } 'the vfox release download'
Invoke-Native { tar -xf $zip -C $WorkDir } 'extracting the vfox release'
Remove-Item $zip
Copy-Item -Path (Get-ChildItem -Path $WorkDir -Recurse -Filter 'vfox.exe') -Destination $VfoxExe
}

function Install-VfoxMain {
$goArch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'amd64' }
$goMsi = "$env:TEMP\go.msi"
& curl.exe -fsSL -o $goMsi "https://go.dev/dl/go1.27.1.windows-$goArch.msi"
Start-Process msiexec.exe -Wait -ArgumentList '/i', "`"$goMsi`"", '/quiet', '/norestart'
Invoke-Native { & curl.exe @CurlRetry -fsSL -o $goMsi "https://go.dev/dl/go1.27.1.windows-$goArch.msi" } 'the Go download'
$goInstall = $null
for ($attempt = 1; $attempt -le 5; $attempt++) {
$goInstall = Start-Process msiexec.exe -Wait -PassThru -ArgumentList '/i', "`"$goMsi`"", '/quiet', '/norestart'
if ($goInstall.ExitCode -in 0, 3010, 1937) { break }
Start-Sleep -Seconds 15
}
if ($goInstall.ExitCode -notin 0, 3010, 1937) { throw "FAIL the Go MSI install exited with code $($goInstall.ExitCode)" }
Remove-Item $goMsi
$src = "$WorkDir\vfox-src"
git clone --depth 1 https://github.com/version-fox/vfox $src
Invoke-Native { git clone --depth 1 https://github.com/version-fox/vfox $src } 'cloning vfox from main'
$buildScript = @"
go build -C "$src" -trimpath -o "$VfoxExe" .
"@
Expand All @@ -37,17 +56,5 @@ go build -C "$src" -trimpath -o "$VfoxExe" .

if ($VfoxVersion -eq 'main') { Install-VfoxMain } else { Install-VfoxRelease }

tar -a -cf $PluginZip -C $RepoRoot metadata.lua hooks lib
if ($LASTEXITCODE -ne 0) { throw "FAIL tar packaging exited with code $LASTEXITCODE" }
vfox add flutter --source $PluginZip
if ($LASTEXITCODE -ne 0) { throw "FAIL vfox add flutter --source exited with code $LASTEXITCODE" }

$ProfileDir = Split-Path -Parent $PROFILE
if (-not (Test-Path $ProfileDir)) {
New-Item -ItemType Directory -Force -Path $ProfileDir | Out-Null
}
if (-not (Test-Path $PROFILE)) {
New-Item -ItemType File -Force -Path $PROFILE | Out-Null
}
Add-Content -Path $PROFILE -Value "`$env:PATH = `"$WorkDir;`$env:PATH`""
Add-Content -Path $PROFILE -Value 'Invoke-Expression "$(vfox activate pwsh)"'
Invoke-Native { tar -a -cf $PluginZip -C $RepoRoot metadata.lua hooks lib } 'tar packaging'
Invoke-Native { vfox add flutter --source $PluginZip } 'vfox add flutter --source'
Loading