|
| 1 | +#!/usr/bin/env pwsh |
| 2 | +# CLI argument test suite for VMAware (Windows / PowerShell) |
| 3 | + |
| 4 | +param([string]$BIN = "build\vmaware.exe") |
| 5 | + |
| 6 | +if (-not (Test-Path $BIN)) { |
| 7 | + Write-Error "Binary not found: $BIN" |
| 8 | + exit 1 |
| 9 | +} |
| 10 | + |
| 11 | +$script:pass = 0 |
| 12 | +$script:fail = 0 |
| 13 | + |
| 14 | +function ok([string]$desc) { Write-Host " PASS $desc"; $script:pass++ } |
| 15 | +function Fail-Test([string]$desc) { Write-Host " FAIL $desc"; $script:fail++ } |
| 16 | + |
| 17 | +# Runs $BIN with the given args, expects exit 0 |
| 18 | +function check([string]$desc, [string[]]$binArgs) { |
| 19 | + $null = & $BIN @binArgs 2>&1 |
| 20 | + if ($LASTEXITCODE -eq 0) { ok $desc } else { Fail-Test $desc } |
| 21 | +} |
| 22 | + |
| 23 | +# Runs $BIN with the given args, expects a non-zero exit code |
| 24 | +function check_fails([string]$desc, [string[]]$binArgs) { |
| 25 | + $null = & $BIN @binArgs 2>&1 |
| 26 | + if ($LASTEXITCODE -ne 0) { ok $desc } else { Fail-Test $desc } |
| 27 | +} |
| 28 | + |
| 29 | +# Captures stdout+stderr, expects output to match $pattern (regex) |
| 30 | +function match_out([string]$desc, [string]$pattern, [string[]]$binArgs) { |
| 31 | + $out = (& $BIN @binArgs 2>&1) -join "`n" |
| 32 | + if ($out -match $pattern) { ok $desc } |
| 33 | + else { Fail-Test "$desc (got: $(($out -split "`n")[0]))" } |
| 34 | +} |
| 35 | + |
| 36 | +# Captures stdout, expects an integer in [$lo, $hi] |
| 37 | +function range_out([string]$desc, [int]$lo, [int]$hi, [string[]]$binArgs) { |
| 38 | + $out = (& $BIN @binArgs 2>$null) -join "" |
| 39 | + if ($LASTEXITCODE -ne 0) { Fail-Test "$desc (non-zero exit)"; return } |
| 40 | + if ($out -match '^\d+$' -and [int]$out -ge $lo -and [int]$out -le $hi) { |
| 41 | + ok $desc |
| 42 | + } else { |
| 43 | + Fail-Test "$desc (got: $out, expected $lo-$hi)" |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +Write-Host "=== vmaware CLI tests ===" |
| 48 | +Write-Host "" |
| 49 | + |
| 50 | +# exit codes |
| 51 | +Write-Host "exit codes" |
| 52 | +check "--help exits 0" @("--help") |
| 53 | +check "--version exits 0" @("--version") |
| 54 | +check "--brand-list exits 0" @("--brand-list") |
| 55 | +check "--detect exits 0" @("--detect") |
| 56 | +check "--percent exits 0" @("--percent") |
| 57 | +check "--brand exits 0" @("--brand") |
| 58 | +check "--type exits 0" @("--type") |
| 59 | +check "--conclusion exits 0" @("--conclusion") |
| 60 | +check "--number exits 0" @("--number") |
| 61 | + |
| 62 | +# --stdout |
| 63 | +$null = & $BIN "--stdout" 2>&1 |
| 64 | +if ($LASTEXITCODE -le 1) { ok "--stdout exits 0 or 1" } else { Fail-Test "--stdout exits 0 or 1" } |
| 65 | + |
| 66 | +check_fails "unknown arg exits non-zero" @("--this-arg-does-not-exist") |
| 67 | + |
| 68 | +# short-flag aliases |
| 69 | +Write-Host "" |
| 70 | +Write-Host "short flag aliases" |
| 71 | +check "-h exits 0" @("-h") |
| 72 | +check "-v exits 0" @("-v") |
| 73 | +check "-a exits 0" @("-a", "--detect") |
| 74 | +check "-d exits 0" @("-d") |
| 75 | +check "-b exits 0" @("-b") |
| 76 | +check "-p exits 0" @("-p") |
| 77 | +check "-c exits 0" @("-c") |
| 78 | +check "-n exits 0" @("-n") |
| 79 | +check "-t exits 0" @("-t") |
| 80 | +check "-l exits 0" @("-l") |
| 81 | + |
| 82 | +# output format |
| 83 | +Write-Host "" |
| 84 | +Write-Host "output format" |
| 85 | +match_out "--detect outputs 0 or 1" '^[01]$' @("--detect") |
| 86 | +range_out "--percent outputs 0-100" 0 100 @("--percent") |
| 87 | +match_out "--number outputs a positive int" '^[1-9][0-9]*$' @("--number") |
| 88 | +match_out "--brand outputs a non-empty line" '.' @("--brand") |
| 89 | +match_out "--type outputs a non-empty line" '.' @("--type") |
| 90 | +match_out "--conclusion outputs a sentence" '.' @("--conclusion") |
| 91 | + |
| 92 | +# no-ansi strips escape codes |
| 93 | +Write-Host "" |
| 94 | +Write-Host "no-ansi" |
| 95 | +$ansiOut = (& $BIN "--no-ansi" 2>&1) -join "`n" |
| 96 | +if ($ansiOut -match '\x1B\[') { |
| 97 | + Fail-Test "--no-ansi still contains ANSI escape codes" |
| 98 | +} else { |
| 99 | + ok "--no-ansi output contains no ANSI escape codes" |
| 100 | +} |
| 101 | + |
| 102 | +# technique count |
| 103 | +Write-Host "" |
| 104 | +Write-Host "technique count" |
| 105 | +$n = (& $BIN "--number" 2>$null) -join "" |
| 106 | +if ($n -match '^\d+$' -and [int]$n -gt 10) { |
| 107 | + ok "--number returns plausible technique count ($n)" |
| 108 | +} else { |
| 109 | + Fail-Test "--number returned unexpected value: $n" |
| 110 | +} |
| 111 | + |
| 112 | +# mutual exclusion |
| 113 | +Write-Host "" |
| 114 | +Write-Host "mutual exclusion" |
| 115 | +check_fails "--detect + --brand rejected" @("--detect", "--brand") |
| 116 | +check_fails "--percent + --brand rejected" @("--percent", "--brand") |
| 117 | +check_fails "--stdout + --detect rejected" @("--stdout", "--detect") |
| 118 | + |
| 119 | +# --disable: valid names |
| 120 | +Write-Host "" |
| 121 | +Write-Host "--disable (valid names)" |
| 122 | +check "--disable single name works" @("--disable", "HYPERVISOR_BIT", "--detect") |
| 123 | +check "--disable multiple space-sep names works" @("--disable", "HYPERVISOR_BIT", "NVRAM", "QEMU_USB", "--detect") |
| 124 | +check "--disable comma-separated names works" @("--disable", "HYPERVISOR_BIT,NVRAM", "--detect") |
| 125 | +check "--disable mixed comma+space works" @("--disable", "HYPERVISOR_BIT,", "NVRAM,", "QEMU_USB", "--detect") |
| 126 | +check "--disable WINE (was WINE_FUNC) works" @("--disable", "WINE", "--detect") |
| 127 | +check "--disable SYSTEM_REGISTERS works" @("--disable", "SYSTEM_REGISTERS", "--detect") |
| 128 | +check "--disable UD works" @("--disable", "UD", "--detect") |
| 129 | +check "--disable HYPERVISOR_HOOK works" @("--disable", "HYPERVISOR_HOOK", "--detect") |
| 130 | +check "--disable SINGLE_STEP works" @("--disable", "SINGLE_STEP", "--detect") |
| 131 | +check "--disable DBVM works" @("--disable", "DBVM", "--detect") |
| 132 | + |
| 133 | +# --disable: invalid names |
| 134 | +Write-Host "" |
| 135 | +Write-Host "--disable (invalid names)" |
| 136 | +check_fails "--disable bogus name fails" @("--disable", "NOT_A_REAL_TECHNIQUE", "--detect") |
| 137 | +check_fails "--disable MULTIPLE (setting) fails" @("--disable", "MULTIPLE", "--detect") |
| 138 | + |
| 139 | +# --disable reflected in general output |
| 140 | +Write-Host "" |
| 141 | +Write-Host "--disable reflected in general output" |
| 142 | +$disOut = (& $BIN "--no-ansi", "--disable", "HYPERVISOR_BIT" 2>&1) -join "`n" |
| 143 | +if ($disOut -match "Skipped CPUID hypervisor bit") { |
| 144 | + ok "--disable HYPERVISOR_BIT shows as skipped in general output" |
| 145 | +} else { |
| 146 | + Fail-Test "--disable HYPERVISOR_BIT not reflected in general output" |
| 147 | +} |
| 148 | + |
| 149 | +# --high-threshold |
| 150 | +Write-Host "" |
| 151 | +Write-Host "--high-threshold" |
| 152 | +$pNormal = [string]((& $BIN "--percent" 2>$null) -join "") |
| 153 | +$pHigh = [string]((& $BIN "--percent", "--high-threshold" 2>$null) -join "") |
| 154 | +$pNormal = if ($pNormal -match '^\d+$') { [int]$pNormal } else { 0 } |
| 155 | +$pHigh = if ($pHigh -match '^\d+$') { [int]$pHigh } else { 0 } |
| 156 | +if ($pNormal -ge $pHigh) { |
| 157 | + ok "--high-threshold produces equal or lower percentage ($pNormal -> $pHigh)" |
| 158 | +} else { |
| 159 | + Fail-Test "--high-threshold produced higher percentage ($pNormal -> $pHigh)" |
| 160 | +} |
| 161 | + |
| 162 | +# --all |
| 163 | +Write-Host "" |
| 164 | +Write-Host "--all" |
| 165 | +check "--all --detect exits 0" @("--all", "--detect") |
| 166 | +check "--all --percent exits 0" @("--all", "--percent") |
| 167 | + |
| 168 | +# --dynamic |
| 169 | +Write-Host "" |
| 170 | +Write-Host "--dynamic" |
| 171 | +check "--dynamic --conclusion exits 0" @("--dynamic", "--conclusion") |
| 172 | + |
| 173 | +# --json |
| 174 | +Write-Host "" |
| 175 | +Write-Host "--json" |
| 176 | +$tmpJson = [System.IO.Path]::GetTempFileName() + ".json" |
| 177 | +try { |
| 178 | + $null = & $BIN "--json", "--output", $tmpJson 2>$null |
| 179 | + if ((Test-Path $tmpJson) -and (Get-Item $tmpJson).Length -gt 0) { |
| 180 | + ok "--json creates a non-empty output file" |
| 181 | + } else { |
| 182 | + Fail-Test "--json did not create an output file" |
| 183 | + } |
| 184 | + $jsonContent = if (Test-Path $tmpJson) { Get-Content $tmpJson -Raw } else { "" } |
| 185 | + if ($jsonContent -match '"is_detected"') { |
| 186 | + ok "--json output contains expected keys" |
| 187 | + } else { |
| 188 | + Fail-Test "--json output missing expected keys" |
| 189 | + } |
| 190 | +} finally { |
| 191 | + if (Test-Path $tmpJson) { Remove-Item $tmpJson -Force } |
| 192 | +} |
| 193 | + |
| 194 | +# --brand-list |
| 195 | +Write-Host "" |
| 196 | +Write-Host "--brand-list" |
| 197 | +$brandLines = (& $BIN "--brand-list" 2>$null) | Where-Object { $_ -ne "" } |
| 198 | +$count = $brandLines.Count |
| 199 | +if ($count -gt 5) { |
| 200 | + ok "--brand-list returns multiple entries ($count lines)" |
| 201 | +} else { |
| 202 | + Fail-Test "--brand-list returned too few entries ($count lines)" |
| 203 | +} |
| 204 | + |
| 205 | +# summary |
| 206 | +Write-Host "" |
| 207 | +Write-Host "===========================" |
| 208 | +Write-Host " Passed: $($script:pass)" |
| 209 | +Write-Host " Failed: $($script:fail)" |
| 210 | +Write-Host "===========================" |
| 211 | +if ($script:fail -ne 0) { exit 1 } |
0 commit comments