Files
Chris LuandGitHub edaee0e426 ci: run each conformance test on its own (#10564)
* ci: run each conformance test on its own

Run as one batch with --no-abort, a test that fails part way leaves its
files behind and the next one fails creating them, so the report showed a
cascade of failures that were really one. The whole rdwr and flush group
passes when run alone, and was only ever collateral.

Each test now gets its own directory and its own invocation, which costs
a process start per test and makes the list mean what it says.

* ci: clear a case directory before reusing it

-Force creates the directory but leaves anything already in it, so a
leftover from an interrupted run would defeat the isolation this exists
to provide.

* ci: list the one real failure isolation exposed

With each test on its own, 31 of 32 pass. The exception is rdwr_mmap_test,
which compares mapped bytes against what was written and finds them
different — a genuine data mismatch that only appeared once the test could
run to completion instead of tripping over a previous one's leftovers.
2026-08-04 13:24:38 -07:00

113 lines
4.7 KiB
PowerShell

# Runs WinFsp's own conformance suite against a mounted SeaweedFS drive.
#
# winfsp-tests is what WinFsp uses to check a filesystem behaves like NTFS,
# and --fuse-external points it at somebody else's filesystem instead of the
# bundled memfs. It is the Windows counterpart of the pjdfstest run the FUSE
# mount already goes through, and it is scored the same way: anything failing
# that is not in known_failures.txt is a regression.
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)][string]$MountPoint,
[string]$KnownFailures = "$PSScriptRoot\known_failures.txt",
[string]$WinFspTestsVersion = '2.1.25156'
)
$ErrorActionPreference = 'Stop'
# "S:" and "S:\" mean different things to Join-Path: without the separator the
# result is relative to the drive's current directory, not its root.
if ($MountPoint -notmatch '[\\/]$') { $MountPoint = $MountPoint + '\' }
# winfsp-tests links against winfsp-x64.dll, which the MSI puts somewhere the
# loader does not look by default.
foreach ($candidate in @("${env:ProgramFiles(x86)}\WinFsp\bin", "$env:ProgramFiles\WinFsp\bin")) {
if (Test-Path $candidate) {
$env:PATH = "$candidate;$env:PATH"
Write-Host "using WinFsp binaries from $candidate"
}
}
$toolDir = Join-Path $env:TEMP 'winfsp-tests'
$exe = Join-Path $toolDir 'winfsp-tests-x64.exe'
if (-not (Test-Path $exe)) {
# Shipped as its own archive rather than in the MSI.
$url = "https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-tests-$WinFspTestsVersion.zip"
$zip = Join-Path $env:TEMP 'winfsp-tests.zip'
Write-Host "downloading $url"
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
Expand-Archive -LiteralPath $zip -DestinationPath $toolDir -Force
}
if (-not (Test-Path $exe)) {
throw "winfsp-tests-x64.exe not found under $toolDir"
}
# A missing or unreadable list would otherwise run with nothing excluded and
# report that as normal, which reads like a pass with no known failures.
if (-not (Test-Path $KnownFailures)) {
throw "known failures list not found at $KnownFailures"
}
$excluded = @(Get-Content $KnownFailures |
ForEach-Object { $_.Trim() } |
Where-Object { $_ -and -not $_.StartsWith('#') })
Write-Host "read $($excluded.Count) exclusions from $KnownFailures"
if ($excluded.Count -eq 0) {
throw "known failures list at $KnownFailures parsed to nothing"
}
# The suite refuses to run anywhere but a drive, and works in the current
# directory, so it has to be driven from inside the mount.
$workDir = Join-Path $MountPoint 'winfsp-conformance'
New-Item -ItemType Directory -Force -Path $workDir | Out-Null
Push-Location $workDir
try {
# --fuse-external: a third-party FUSE filesystem, not the bundled memfs.
# --resilient: tolerate operations this filesystem does not implement.
#
# One test per invocation, each in its own directory. Batched with
# --no-abort, a test that failed part way left its files behind and the
# next one failed creating them, reporting a cascade of failures that were
# really one. This costs a process start per test and makes the list mean
# what it says.
$base = @('--fuse-external', '--resilient')
$names = @(& $exe @base '--list' 2>&1 |
ForEach-Object { if ($_ -match '^([a-z_0-9]+)\s*$') { $Matches[1] } })
if ($names.Count -eq 0) { throw "could not list tests" }
Write-Host "listed $($names.Count) tests"
$failed = @()
$ran = 0
foreach ($name in $names) {
if ($excluded | Where-Object { $name -like $_ }) { continue }
$ran++
$caseDir = Join-Path $workDir $name
# -Force creates the directory but leaves anything already in it, and
# a leftover file is the very thing this isolation exists to avoid.
Remove-Item $caseDir -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $caseDir | Out-Null
Push-Location $caseDir
try {
$out = & $exe @base $name 2>&1
$out | ForEach-Object { Write-Host $_ }
if ($out -match '\s+KO\s*$' -or $LASTEXITCODE -ne 0) { $failed += $name }
} finally {
Pop-Location
Remove-Item $caseDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
Write-Host "ran $ran tests, $($failed.Count) failed"
$code = if ($failed.Count -gt 0) { 1 } else { 0 }
} finally {
Pop-Location
Remove-Item $workDir -Recurse -Force -ErrorAction SilentlyContinue
}
if ($failed.Count -gt 0) {
Write-Host "::error::winfsp-tests failures outside known_failures.txt: $($failed -join ', ')"
exit 1
}
if ($code -ne 0) {
Write-Host "::error::winfsp-tests exited $code with no failure reported"
exit $code
}
Write-Host 'winfsp-tests passed'