aboutsummaryrefslogtreecommitdiff
path: root/scripts/test_windows
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-21 21:43:22 +0100
committerGitHub Enterprise <[email protected]>2026-03-21 21:43:22 +0100
commit14ca5b35d0fc477ba30f10b80f937b523fd7e930 (patch)
tree8aab2acfec8be1af4bf0dffdb4badc3b64bf8385 /scripts/test_windows
parentfix null stats provider crash when build store is not configured (#875) (diff)
downloadzen-14ca5b35d0fc477ba30f10b80f937b523fd7e930.tar.xz
zen-14ca5b35d0fc477ba30f10b80f937b523fd7e930.zip
Interprocess pipe support (for stdout/stderr capture) (#866)
- **RAII pipe handles for child process stdout/stderr capture**: `StdoutPipeHandles` is now a proper RAII type with automatic cleanup, move semantics, and partial close support. This makes it safe to use pipes for capturing child process output without risking handle/fd leaks. - **Optional separate stderr pipe**: `CreateProcOptions` now accepts a `StderrPipe` field so callers can capture stdout and stderr independently. When null (default), stderr shares the stdout pipe as before. - **LogStreamListener with pluggable handler**: The TCP log stream listener accepts connections from remote processes and delivers parsed log lines through a `LogStreamHandler` interface, set dynamically via `SetHandler()`. This allows any client to receive log messages without depending on a specific console implementation. - **TcpLogStreamSink for zen::logging**: A logging sink that forwards log messages to a `LogStreamListener` over TCP, using the native `zen::logging::Sink` infrastructure with proper thread-safe synchronization. - **Reliable child process exit codes on Linux**: `waitpid` result handling is fixed so `ProcessHandle::GetExitCode()` returns the real exit code. `ProcessHandle::Reset()` reaps zombies directly, replacing the global `IgnoreChildSignals()` which prevented exit code collection entirely. Also fixes a TOCTOU race in `ProcessHandle::Wait()` on Linux/Mac. - **Pipe capture test suite**: Tests covering stdout/stderr capture via pipes (both shared and separate modes), RAII cleanup, move semantics, and exit code propagation using `zentest-appstub` as the child process. - **Service command integration tests**: Shell-based integration tests for `zen service` covering the full lifecycle (install, status, start, stop, uninstall) on all three platforms — Linux (systemd), macOS (launchd), and Windows (SCM via PowerShell). - **Test script reorganization**: Platform-specific test scripts moved from `scripts/test_scripts/` into `scripts/test_linux/`, `test_mac/`, and `test_windows/`.
Diffstat (limited to 'scripts/test_windows')
-rw-r--r--scripts/test_windows/block-clone-test.ps1145
-rw-r--r--scripts/test_windows/service-test.ps1353
2 files changed, 498 insertions, 0 deletions
diff --git a/scripts/test_windows/block-clone-test.ps1 b/scripts/test_windows/block-clone-test.ps1
new file mode 100644
index 000000000..aa6ec3a39
--- /dev/null
+++ b/scripts/test_windows/block-clone-test.ps1
@@ -0,0 +1,145 @@
+# Test block-clone functionality on a temporary ReFS VHD.
+#
+# Requires:
+# - Administrator privileges
+# - Windows Server, or Windows 10/11 Pro for Workstations (ReFS support)
+# - Hyper-V PowerShell module (for New-VHD), or diskpart fallback
+#
+# Usage:
+# # From an elevated PowerShell prompt:
+# .\scripts\test_windows\block-clone-test.ps1 [-TestBinary <path>]
+#
+# If -TestBinary is not given, defaults to build\windows\x64\debug\zencore-test.exe
+# relative to the repository root.
+
+param(
+ [string]$TestBinary = ""
+)
+
+$ErrorActionPreference = "Stop"
+
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
+$RepoRoot = (Resolve-Path "$ScriptDir\..\..").Path
+
+if (-not $TestBinary) {
+ $TestBinary = Join-Path $RepoRoot "build\windows\x64\debug\zencore-test.exe"
+}
+
+$ImageSizeMB = 2048
+$TestCases = "TryCloneFile,CopyFile.Clone,SupportsBlockRefCounting,CloneQueryInterface"
+
+$VhdPath = ""
+$MountLetter = ""
+
+function Cleanup {
+ $ErrorActionPreference = "SilentlyContinue"
+
+ if ($MountLetter) {
+ Write-Host "Dismounting VHD ..."
+ Dismount-VHD -Path $VhdPath -ErrorAction SilentlyContinue
+ }
+ if ($VhdPath -and (Test-Path $VhdPath)) {
+ Remove-Item -Force $VhdPath -ErrorAction SilentlyContinue
+ }
+}
+
+trap {
+ Cleanup
+ throw $_
+}
+
+# --- Preflight checks ---
+
+$IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
+ [Security.Principal.WindowsBuiltInRole]::Administrator)
+if (-not $IsAdmin) {
+ Write-Error "This script must be run as Administrator (for VHD mount/format)."
+ exit 1
+}
+
+if (-not (Test-Path $TestBinary)) {
+ Write-Error "Test binary not found: $TestBinary`nHint: build with 'xmake config -m debug && xmake build zencore-test'"
+ exit 1
+}
+
+# Check that ReFS formatting is available
+$RefsAvailable = $true
+try {
+ # A quick check: on non-Server/Workstation SKUs, Format-Volume -FileSystem ReFS will fail
+ $OsCaption = (Get-CimInstance Win32_OperatingSystem).Caption
+ if ($OsCaption -notmatch "Server|Workstation|Enterprise") {
+ Write-Warning "ReFS may not be available on this Windows edition: $OsCaption"
+ Write-Warning "Continuing anyway — format step will fail if unsupported."
+ }
+} catch {
+ # Non-fatal, just proceed
+}
+
+# --- Create and mount ReFS VHD ---
+
+$VhdPath = Join-Path $env:TEMP "refs-clone-test-$([guid]::NewGuid().ToString('N').Substring(0,8)).vhdx"
+
+Write-Host "Creating ${ImageSizeMB}MB VHDX at $VhdPath ..."
+
+try {
+ # Prefer Hyper-V cmdlet if available
+ New-VHD -Path $VhdPath -SizeBytes ($ImageSizeMB * 1MB) -Fixed | Out-Null
+} catch {
+ # Fallback to diskpart
+ Write-Host "New-VHD not available, falling back to diskpart ..."
+ $DiskpartScript = @"
+create vdisk file="$VhdPath" maximum=$ImageSizeMB type=fixed
+"@
+ $DiskpartScript | diskpart | Out-Null
+}
+
+Write-Host "Mounting and initializing VHD ..."
+
+Mount-VHD -Path $VhdPath
+$Disk = Get-VHD -Path $VhdPath | Get-Disk
+
+# Suppress Explorer's auto-open / "format disk?" prompts for the raw partition
+Stop-Service ShellHWDetection -ErrorAction SilentlyContinue
+
+try {
+ Initialize-Disk -Number $Disk.Number -PartitionStyle GPT -ErrorAction SilentlyContinue
+ $Partition = New-Partition -DiskNumber $Disk.Number -UseMaximumSize -AssignDriveLetter
+ $MountLetter = $Partition.DriveLetter
+
+ Write-Host "Formatting ${MountLetter}: as ReFS with integrity disabled ..."
+ Format-Volume -DriveLetter $MountLetter -FileSystem ReFS -NewFileSystemLabel "CloneTest" -Confirm:$false | Out-Null
+
+ # Disable integrity streams (required for block cloning to work on ReFS)
+ Set-FileIntegrity "${MountLetter}:\" -Enable $false -ErrorAction SilentlyContinue
+} finally {
+ Start-Service ShellHWDetection -ErrorAction SilentlyContinue
+}
+
+$MountRoot = "${MountLetter}:\"
+
+# --- Copy test binary and run ---
+
+Write-Host "Copying test binary to ReFS volume ..."
+Copy-Item $TestBinary "$MountRoot\zencore-test.exe"
+
+Write-Host "Running block-clone tests ..."
+Write-Host "---"
+
+$proc = Start-Process -FilePath "$MountRoot\zencore-test.exe" `
+ -ArgumentList "--test-suite=core.filesystem", "--test-case=$TestCases" `
+ -NoNewWindow -Wait -PassThru
+
+Write-Host "---"
+
+if ($proc.ExitCode -ne 0) {
+ Write-Error "Tests failed with exit code $($proc.ExitCode)"
+ Cleanup
+ exit $proc.ExitCode
+}
+
+Write-Host "ReFS: all block-clone tests passed."
+
+# --- Cleanup ---
+
+Cleanup
+Write-Host "Done."
diff --git a/scripts/test_windows/service-test.ps1 b/scripts/test_windows/service-test.ps1
new file mode 100644
index 000000000..4c484c63f
--- /dev/null
+++ b/scripts/test_windows/service-test.ps1
@@ -0,0 +1,353 @@
+# Test zen service command lifecycle on Windows (SCM).
+#
+# Requires: Administrator privileges
+#
+# Usage:
+# # From an elevated PowerShell prompt:
+# .\scripts\test_windows\service-test.ps1 [-ZenBinary <path>]
+#
+# If -ZenBinary is not given, defaults to build\windows\x64\debug\zen.exe
+# relative to the repository root.
+
+param(
+ [string]$ZenBinary
+)
+
+$ErrorActionPreference = "Stop"
+
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$RepoRoot = (Resolve-Path "$ScriptDir\..\..").Path
+
+if (-not $ZenBinary) {
+ $ZenBinary = Join-Path $RepoRoot "build\windows\x64\debug\zen.exe"
+}
+$ZenServerBinary = Join-Path (Split-Path -Parent $ZenBinary) "zenserver.exe"
+$ServiceName = "ZenServerTest-$PID"
+
+$Script:Passed = 0
+$Script:Failed = 0
+$Script:TestsRun = 0
+
+function Pass($Message) {
+ $Script:TestsRun++
+ $Script:Passed++
+ Write-Host " PASS: $Message" -ForegroundColor Green
+}
+
+function Fail($Message, $Detail) {
+ $Script:TestsRun++
+ $Script:Failed++
+ Write-Host " FAIL: $Message" -ForegroundColor Red
+ if ($Detail) {
+ Write-Host " $Detail"
+ }
+}
+
+function Cleanup {
+ Write-Host ""
+ Write-Host "--- Cleanup ---"
+
+ # Stop the service if running
+ $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
+ if ($svc -and $svc.Status -eq "Running") {
+ Write-Host "Stopping test service..."
+ Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue
+ Start-Sleep -Seconds 2
+ }
+
+ # Delete the service if it exists
+ if (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue) {
+ Write-Host "Removing test service..."
+ sc.exe delete $ServiceName 2>$null | Out-Null
+ }
+
+ Write-Host ""
+ Write-Host "=============================="
+ Write-Host " Tests run: $Script:TestsRun"
+ Write-Host " Passed: $Script:Passed" -ForegroundColor Green
+ if ($Script:Failed -gt 0) {
+ Write-Host " Failed: $Script:Failed" -ForegroundColor Red
+ } else {
+ Write-Host " Failed: $Script:Failed"
+ }
+ Write-Host "=============================="
+}
+
+# ── Preflight checks ──────────────────────────────────────────────
+
+$IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
+ [Security.Principal.WindowsBuiltInRole]::Administrator)
+
+if (-not $IsAdmin) {
+ Write-Host "Error: this test must be run from an elevated (Administrator) prompt." -ForegroundColor Red
+ exit 1
+}
+
+if (-not (Test-Path $ZenBinary)) {
+ Write-Host "Error: zen binary not found at '$ZenBinary'" -ForegroundColor Red
+ Write-Host "Build with: xmake config -m debug && xmake build zen"
+ exit 1
+}
+
+if (-not (Test-Path $ZenServerBinary)) {
+ Write-Host "Error: zenserver binary not found at '$ZenServerBinary'" -ForegroundColor Red
+ Write-Host "Build with: xmake config -m debug && xmake build zenserver"
+ exit 1
+}
+
+Write-Host "zen binary: $ZenBinary"
+Write-Host "zenserver binary: $ZenServerBinary"
+Write-Host "service name: $ServiceName"
+Write-Host ""
+
+try {
+
+# ── Test: status before install (should fail) ─────────────────────
+
+Write-Host "--- Test: status before install ---"
+
+$Output = & $ZenBinary service status $ServiceName 2>&1 | Out-String
+if ($Output -match "not installed") {
+ Pass "status reports 'not installed' for non-existent service"
+} else {
+ Fail "status should report 'not installed'" "got: $Output"
+}
+
+# ── Test: install ─────────────────────────────────────────────────
+
+Write-Host "--- Test: install ---"
+
+$Output = & $ZenBinary service install $ZenServerBinary $ServiceName --allow-elevation 2>&1 | Out-String
+$ExitCode = $LASTEXITCODE
+
+if ($ExitCode -eq 0) {
+ Pass "install exits with code 0"
+} else {
+ Fail "install exits with code 0" "got exit code: $ExitCode, output: $Output"
+}
+
+$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
+if ($svc) {
+ Pass "service registered in SCM"
+} else {
+ Fail "service registered in SCM"
+}
+
+# Verify service configuration
+if ($svc) {
+ $svcWmi = Get-CimInstance -ClassName Win32_Service -Filter "Name='$ServiceName'" -ErrorAction SilentlyContinue
+ if ($svcWmi -and $svcWmi.PathName -match "zenserver") {
+ Pass "service binary path contains zenserver"
+ } else {
+ Fail "service binary path contains zenserver" "got: $($svcWmi.PathName)"
+ }
+
+ if ($svcWmi -and $svcWmi.StartMode -eq "Auto") {
+ Pass "service is set to auto-start"
+ } else {
+ Fail "service is set to auto-start" "got: $($svcWmi.StartMode)"
+ }
+}
+
+# ── Test: install again (already installed, no --full) ────────────
+
+Write-Host "--- Test: install again (idempotent) ---"
+
+$Output = & $ZenBinary service install $ZenServerBinary $ServiceName --allow-elevation 2>&1 | Out-String
+$ExitCode = $LASTEXITCODE
+
+if ($ExitCode -eq 0) {
+ Pass "re-install exits with code 0"
+} else {
+ Fail "re-install exits with code 0" "got exit code: $ExitCode"
+}
+
+if ($Output -match "already installed") {
+ Pass "re-install reports service already installed"
+} else {
+ Fail "re-install reports service already installed" "got: $Output"
+}
+
+# ── Test: status after install (not yet started) ──────────────────
+
+Write-Host "--- Test: status after install (stopped) ---"
+
+$Output = & $ZenBinary service status $ServiceName 2>&1 | Out-String
+if ($Output -match "not running") {
+ Pass "status reports 'not running' for stopped service"
+} else {
+ Fail "status reports 'not running' for stopped service" "got: $Output"
+}
+
+# ── Test: start ───────────────────────────────────────────────────
+
+Write-Host "--- Test: start ---"
+
+$Output = & $ZenBinary service start $ServiceName --allow-elevation 2>&1 | Out-String
+$ExitCode = $LASTEXITCODE
+
+if ($ExitCode -eq 0) {
+ Pass "start exits with code 0"
+} else {
+ Fail "start exits with code 0" "got exit code: $ExitCode, output: $Output"
+}
+
+Start-Sleep -Seconds 2
+
+$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
+if ($svc -and $svc.Status -eq "Running") {
+ Pass "service is running after start"
+
+ # ── Test: status while running ────────────────────────────────
+
+ Write-Host "--- Test: status (running) ---"
+
+ $Output = & $ZenBinary service status $ServiceName 2>&1 | Out-String
+ if ($Output -match "Running") {
+ Pass "status reports 'Running'"
+ } else {
+ Fail "status reports 'Running'" "got: $Output"
+ }
+
+ if ($Output -match "zenserver") {
+ Pass "status shows executable path"
+ } else {
+ Fail "status shows executable path" "got: $Output"
+ }
+
+ # ── Test: start again (already running) ───────────────────────
+
+ Write-Host "--- Test: start again (already running) ---"
+
+ $Output = & $ZenBinary service start $ServiceName --allow-elevation 2>&1 | Out-String
+ if ($Output -match "already running") {
+ Pass "start reports service already running"
+ } else {
+ Fail "start reports service already running" "got: $Output"
+ }
+
+ # ── Test: stop ────────────────────────────────────────────────
+
+ Write-Host "--- Test: stop ---"
+
+ $Output = & $ZenBinary service stop $ServiceName --allow-elevation 2>&1 | Out-String
+ $ExitCode = $LASTEXITCODE
+
+ if ($ExitCode -eq 0) {
+ Pass "stop exits with code 0"
+ } else {
+ Fail "stop exits with code 0" "got exit code: $ExitCode, output: $Output"
+ }
+
+ Start-Sleep -Seconds 2
+
+ $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
+ if ($svc -and $svc.Status -ne "Running") {
+ Pass "service is not running after stop"
+ } else {
+ Fail "service is not running after stop"
+ }
+} else {
+ Fail "service is running after start" "(skipping start-dependent tests)"
+}
+
+# ── Test: stop when already stopped ───────────────────────────────
+
+Write-Host "--- Test: stop when already stopped ---"
+
+Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue
+Start-Sleep -Seconds 1
+
+$Output = & $ZenBinary service stop $ServiceName --allow-elevation 2>&1 | Out-String
+if ($Output -match "not running") {
+ Pass "stop reports 'not running' when already stopped"
+} else {
+ Fail "stop reports 'not running' when already stopped" "got: $Output"
+}
+
+# ── Test: uninstall while running (should fail) ───────────────────
+
+Write-Host "--- Test: uninstall while running (should fail) ---"
+
+& $ZenBinary service start $ServiceName --allow-elevation 2>&1 | Out-Null
+Start-Sleep -Seconds 2
+
+$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
+if ($svc -and $svc.Status -eq "Running") {
+ $Output = & $ZenBinary service uninstall $ServiceName --allow-elevation 2>&1 | Out-String
+ $ExitCode = $LASTEXITCODE
+
+ if ($ExitCode -ne 0 -or $Output -match "running.*stop") {
+ Pass "uninstall refuses while service is running"
+ } else {
+ Fail "uninstall refuses while service is running" "got: exit=$ExitCode, output: $Output"
+ }
+
+ # Stop it for the real uninstall test
+ & $ZenBinary service stop $ServiceName --allow-elevation 2>&1 | Out-Null
+ Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue
+ Start-Sleep -Seconds 2
+} else {
+ Write-Host " (skipped: could not start service for this test)"
+}
+
+# ── Test: uninstall ───────────────────────────────────────────────
+
+Write-Host "--- Test: uninstall ---"
+
+Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue
+Start-Sleep -Seconds 1
+
+$Output = & $ZenBinary service uninstall $ServiceName --allow-elevation 2>&1 | Out-String
+$ExitCode = $LASTEXITCODE
+
+if ($ExitCode -eq 0) {
+ Pass "uninstall exits with code 0"
+} else {
+ Fail "uninstall exits with code 0" "got exit code: $ExitCode, output: $Output"
+}
+
+$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
+if (-not $svc) {
+ Pass "service removed from SCM after uninstall"
+} else {
+ Fail "service removed from SCM after uninstall"
+}
+
+# ── Test: status after uninstall ──────────────────────────────────
+
+Write-Host "--- Test: status after uninstall ---"
+
+$Output = & $ZenBinary service status $ServiceName 2>&1 | Out-String
+if ($Output -match "not installed") {
+ Pass "status reports 'not installed' after uninstall"
+} else {
+ Fail "status reports 'not installed' after uninstall" "got: $Output"
+}
+
+# ── Test: uninstall when not installed (idempotent) ───────────────
+
+Write-Host "--- Test: uninstall when not installed ---"
+
+$Output = & $ZenBinary service uninstall $ServiceName --allow-elevation 2>&1 | Out-String
+$ExitCode = $LASTEXITCODE
+
+if ($ExitCode -eq 0) {
+ Pass "uninstall of non-existent service exits with code 0"
+} else {
+ Fail "uninstall of non-existent service exits with code 0" "got exit code: $ExitCode"
+}
+
+if ($Output -match "not installed") {
+ Pass "uninstall reports service not installed"
+} else {
+ Fail "uninstall reports service not installed" "got: $Output"
+}
+
+} finally {
+ Cleanup
+}
+
+if ($Script:Failed -gt 0) {
+ exit 1
+}