msquic/scripts/run-gtest.ps1

931 строка
31 KiB
PowerShell

<#
.SYNOPSIS
This script runs a google test executable and collects logs or dumps
as necessary.
.PARAMETER Path
The path to the test executable.
.PARAMETER Kernel
Runs for Windows kernel mode, given the path for binaries.
.PARAMETER Filter
A filter to include test cases from the list to execute. Multiple filters
are separated by :. Negative filters are prefixed with -.
.PARAMETER ListTestCases
Lists all the test cases.
.PARAMETER IsolationMode
Controls the isolation mode when running each test case.
.PARAMETER KeepOutputOnSuccess
Don't discard console output or logs on success.
.PARAMETER GenerateXmlResults
Generates an xml Test report for the run.
.PARAMETER Debugger
Attaches the debugger to the process.
.PARAMETER InitialBreak
Debugger starts broken into the process to allow setting breakpoints, etc.
.PARAMETER BreakOnFailure
Triggers a break point on a test failure.
.PARAMETER LogProfile
The name of the profile to use for log collection.
.PARAMETER CompressOutput
Compresses the output files generated for failed test cases.
.PARAMETER NoProgress
Disables the progress bar.
.Parameter EnableAppVerifier
Enables all basic Application Verifier checks on the test binary.
.Parameter EnableSystemVerifier
Enables TCPIP verifier in user mode tests.
.Parameter CodeCoverage
Collects code coverage for this test run. Incompatible with -Debugger.
.Parameter AZP
Runs in Azure Pipelines mode.
.Parameter ErrorsAsWarnings
Treats all errors as warnings.
.PARAMETER DuoNic
Uses DuoNic instead of loopback.
#>
param (
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $false)]
[string]$Kernel = "",
[Parameter(Mandatory = $false)]
[string]$Filter = "",
[Parameter(Mandatory = $false)]
[switch]$ListTestCases = $false,
[Parameter(Mandatory = $false)]
[ValidateSet("Batch", "Isolated")]
[string]$IsolationMode = "Isolated",
[Parameter(Mandatory = $false)]
[switch]$KeepOutputOnSuccess = $false,
[Parameter(Mandatory = $false)]
[switch]$GenerateXmlResults = $false,
[Parameter(Mandatory = $false)]
[switch]$Debugger = $false,
[Parameter(Mandatory = $false)]
[switch]$InitialBreak = $false,
[Parameter(Mandatory = $false)]
[switch]$BreakOnFailure = $false,
[Parameter(Mandatory = $false)]
[ValidateSet("None", "Basic.Light", "Basic.Verbose", "Full.Light", "Full.Verbose")]
[string]$LogProfile = "None",
[Parameter(Mandatory = $false)]
[switch]$CompressOutput = $false,
[Parameter(Mandatory = $false)]
[switch]$NoProgress = $false,
[Parameter(Mandatory = $false)]
[switch]$EnableAppVerifier = $false,
[Parameter(Mandatory = $false)]
[switch]$EnableSystemVerifier = $false,
[Parameter(Mandatory = $false)]
[switch]$CodeCoverage = $false,
[Parameter(Mandatory = $false)]
[String]$PfxPath = "",
[Parameter(Mandatory = $false)]
[switch]$AZP = $false,
[Parameter(Mandatory = $false)]
[switch]$GHA = $false,
[Parameter(Mandatory = $false)]
[switch]$ErrorsAsWarnings = $false,
[Parameter(Mandatory = $false)]
[string]$ExtraArtifactDir = "",
[Parameter(Mandatory = $false)]
[switch]$DuoNic = $false,
[Parameter(Mandatory = $false)]
[string]$OsRunner = "",
[Parameter(Mandatory = $false)]
[switch]$UseQtip = $false
)
Set-StrictMode -Version 'Latest'
$PSDefaultParameterValues['*:ErrorAction'] = 'Stop'
function Test-Administrator
{
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
function Log($msg) {
Write-Host "[$(Get-Date)] $msg"
}
function LogWrn($msg) {
if ($AZP -and !$ErrorsAsWarnings) {
Write-Host "##vso[task.LogIssue type=warning;][$(Get-Date)] $msg"
} elseif ($GHA -and !$ErrorsAsWarnings) {
Write-Host "::warning::[$(Get-Date)] $msg"
} else {
Write-Warning "[$(Get-Date)] $msg"
}
}
function LogErr($msg) {
if ($AZP -and !$ErrorsAsWarnings) {
Write-Host "##vso[task.LogIssue type=error;][$(Get-Date)] $msg"
} elseif ($GHA -and !$ErrorsAsWarnings) {
Write-Host "::error::[$(Get-Date)] $msg"
} else {
Write-Warning "[$(Get-Date)] $msg"
}
}
function LogFatal($msg) {
if ($AZP -and !$ErrorsAsWarnings) {
Write-Error "##vso[task.LogIssue type=error;][$(Get-Date)] $msg"
} elseif ($GHA -and !$ErrorsAsWarnings) {
Write-Error "::error::[$(Get-Date)] $msg"
} else {
Write-Error "[$(Get-Date)] $msg"
}
}
# Make sure the test executable is present.
if (!(Test-Path $Path)) {
Write-Error "$($Path) does not exist!"
}
# Validate the the kernel switch.
if ($Kernel -ne "" -and !$IsWindows) {
Write-Error "-Kernel switch only supported on Windows";
}
# Validate the code coverage switch
if ($CodeCoverage) {
if (!$IsWindows) {
Write-Error "-CodeCoverage switch only supported on Windows";
}
if ($Debugger) {
Write-Error "-CodeCoverage switch is not supported with debugging";
}
if ($Kernel -ne "") {
Write-Error "-CodeCoverage is not supported for kernel mode tests";
}
if (!(Test-Path "C:\Program Files\OpenCppCoverage\OpenCppCoverage.exe")) {
Write-Error "Code coverage tools are not installed";
}
}
# Root directory of the project.
$RootDir = Split-Path $PSScriptRoot -Parent
# Script for controlling loggings.
$LogScript = Join-Path $RootDir "scripts" "log.ps1"
# Executable name.
$TestExeName = Split-Path $Path -Leaf
$CoverageName = "$(Split-Path $Path -LeafBase).cov"
$ExeLogFolder = $TestExeName
if (![string]::IsNullOrWhiteSpace($ExtraArtifactDir)) {
$ExeLogFolder += "_$ExtraArtifactDir"
}
# Folder for log files.
$LogDir = Join-Path $RootDir "artifacts" "logs" $ExeLogFolder (Get-Date -UFormat "%m.%d.%Y.%T").Replace(':','.')
New-Item -Path $LogDir -ItemType Directory -Force | Out-Null
# Folder for coverage files
$CoverageDir = $null
if ($CodeCoverage) {
$CoverageDir = Join-Path $RootDir "artifacts" "coverage"
New-Item -Path $CoverageDir -ItemType Directory -Force | Out-Null
}
# The file path of the final XML results.
$FinalResultsPath = "$($LogDir)-results.xml"
# Base XML results data.
$XmlResults = [xml]@"
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="0" failures="0" disabled="0" errors="0" time="0" timestamp="date" name="AllTests">
</testsuites>
"@
$XmlResults.testsuites.timestamp = Get-Date -UFormat "%Y-%m-%dT%T"
# XML for creating new (failure) result data.
$FailXmlText = @"
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="1" failures="1" disabled="0" errors="0" time="0" name="AllTests">
<testsuite name="TestSuiteName" tests="1" failures="1" disabled="0" errors="0" timestamp="date" time="0" >
<testcase name="TestCaseName" status="run" result="completed" time="0" timestamp="date" classname="TestSuiteName">
<failure message="Application Crashed" type=""><![CDATA[Application Crashed]]></failure>
</testcase>
</testsuite>
</testsuites>
"@
# Global state for tracking if any crashes occurred.
$global:CrashedProcessCount = 0
# Path to the WER registry key used for collecting dumps.
$WerDumpRegPath = "HKLM:\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\$TestExeName"
# Helper script to build up a combined XML file for all test cases. This
# function just appends the given test case's xml output to the existing xml
# file. If not present (because of a crash) it generates one instead and appends
# it.
function Add-XmlResults($TestCase) {
$TestHasResults = Test-Path $TestCase.ResultsPath
$TestSuiteName = $TestCase.Name.Split(".")[0]
$TestCaseName = $TestCase.Name.Split(".")[1]
$NewXmlResults = $null
if ($TestHasResults) {
# Get the results from the test output.
$NewXmlResults = [xml](Get-Content $TestCase.ResultsPath)
Remove-Item $TestCase.ResultsPath -Force | Out-Null
} else {
# Generate our own results xml.
$NewXmlText = $FailXmlText.Replace("TestSuiteName", $TestSuiteName)
$NewXmlText = $NewXmlText.Replace("TestCaseName", $TestCaseName)
$NewXmlText = $NewXmlText.Replace("date", $TestCase.Timestamp)
$NewXmlResults = [xml]($NewXmlText)
}
$IsFailure = $NewXmlResults.testsuites.failures -eq 1
$Time = $NewXmlResults.testsuites.testsuite.testcase.time -as [Decimal]
$Node = $null
if ($XmlResults.testsuites.tests -ne 0) {
# Look for a matching test suite that might already exist.
$Node = $XmlResults.testsuites.testsuite | Where-Object { $_.Name -eq $TestSuiteName }
}
if ($null -ne $Node) {
# Already has a matching test suite. Add the test case to it.
$Node.tests = ($Node.tests -as [Int]) + 1
if ($IsFailure) {
$Node.failures = ($Node.failures -as [Int]) + 1
}
$Node.time = ($Node.time -as [Decimal]) + $Time
$NewNode = $XmlResults.ImportNode($NewXmlResults.testsuites.testsuite.testcase, $true)
$Node.AppendChild($NewNode) | Out-Null
} else {
# First instance of this test suite. Add the test suite.
$NewNode = $XmlResults.ImportNode($NewXmlResults.testsuites.testsuite, $true)
$XmlResults.testsuites.AppendChild($NewNode) | Out-Null
}
# Update the top level test and failure counts.
$XmlResults.testsuites.tests = ($XmlResults.testsuites.tests -as [Int]) + 1
if ($IsFailure) {
$XmlResults.testsuites.failures = ($XmlResults.testsuites.failures -as [Int]) + 1
}
$XmlResults.testsuites.time = ($XmlResults.testsuites.time -as [Decimal]) + $Time
}
# Asynchronously starts the test executable with the given arguments.
function Start-TestExecutable([String]$Arguments, [String]$OutputDir) {
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
if ($IsWindows) {
if ($Debugger) {
if (Get-Command "windbgx.exe" -ErrorAction SilentlyContinue) {
$pinfo.FileName = "windbgx.exe"
} else {
$pinfo.FileName = "windbg.exe"
}
if ($InitialBreak) {
$pinfo.Arguments = "-G $($Path) $($Arguments)"
} else {
$pinfo.Arguments = "-g -G $($Path) $($Arguments)"
}
} elseif ($CodeCoverage) {
$CoverageOutput = Join-Path $OutputDir $CoverageName
$pinfo.FileName = "C:\Program Files\OpenCppCoverage\OpenCppCoverage.exe"
$pinfo.Arguments = "--modules=$(Split-Path $Path -Parent) --cover_children --sources src\core --excluded_sources unittest --working_dir $($OutputDir) --export_type binary:$($CoverageOutput) -- $($Path) $($Arguments)"
$pinfo.WorkingDirectory = $OutputDir
} else {
$pinfo.FileName = $Path
$pinfo.Arguments = $Arguments
if (Test-Administrator) {
# Enable WER dump collection.
New-ItemProperty -Path $WerDumpRegPath -Name DumpType -PropertyType DWord -Value 2 -Force | Out-Null
New-ItemProperty -Path $WerDumpRegPath -Name DumpFolder -PropertyType ExpandString -Value $OutputDir -Force | Out-Null
}
}
} else {
if ($Debugger) {
$pinfo.FileName = "gdb"
if ($InitialBreak) {
$pinfo.Arguments = "--args $($Path) $($Arguments)"
} else {
$pinfo.Arguments = "-ex=r --args $($Path) $($Arguments)"
}
} else {
$pinfo.FileName = "bash"
$pinfo.Arguments = "-c `"ulimit -c unlimited && LSAN_OPTIONS=report_objects=1 ASAN_OPTIONS=disable_coredump=0:abort_on_error=1 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 $($Path) $($Arguments) && echo Done`""
$pinfo.WorkingDirectory = $OutputDir
}
}
if (!$Debugger) {
$pinfo.RedirectStandardOutput = $true
$pinfo.RedirectStandardError = $true
}
$pinfo.UseShellExecute = $false
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p
}
# Asynchronously starts a single msquictest test case running.
function Start-TestCase([String]$Name) {
$InstanceName = $Name.Replace("/", "_")
$LocalLogDir = Join-Path $LogDir $InstanceName
mkdir $LocalLogDir | Out-Null
if ($LogProfile -ne "None") {
# Start the logs
& $LogScript -Start -Profile $LogProfile | Out-Null
}
# Build up the argument list.
$ResultsPath = Join-Path $LocalLogDir "results.xml"
$Arguments = "--gtest_catch_exceptions=0 --gtest_filter=$($Name) --gtest_output=xml:$($ResultsPath) --timeout 60000"
if ($BreakOnFailure) {
$Arguments += " --gtest_break_on_failure"
}
if ($Kernel -ne "") {
$Arguments += " --kernelPriv"
}
if ($DuoNic) {
$Arguments += " --duoNic"
}
if ($UseQtip) {
$Arguments += " --useQTIP"
}
if ("" -ne $OsRunner) {
$Arguments += " --osRunner=$OsRunner"
}
if ($PfxPath -ne "") {
$Arguments += " -PfxPath:$PfxPath"
}
# Start the test process and return some information about the test case.
[pscustomobject]@{
Name = $Name
InstanceName = $InstanceName
LogDir = $LocalLogDir
StartTime = Get-Date
Timestamp = (Get-Date -UFormat "%Y-%m-%dT%T")
ResultsPath = $ResultsPath
Process = (Start-TestExecutable $Arguments $LocalLogDir)
}
}
# Asynchronously start all the msquictest test cases running.
function Start-AllTestCases {
$Name = "all"
$InstanceName = $Name
if ($LogProfile -ne "None") {
# Start the logs
& $LogScript -Start -Profile $LogProfile | Out-Null
}
# Build up the argument list.
$Arguments = "--gtest_catch_exceptions=0 --gtest_output=xml:$($FinalResultsPath)"
if ($Filter -ne "") {
$Arguments += " --gtest_filter=$($Filter)"
}
if ($BreakOnFailure) {
$Arguments += " --gtest_break_on_failure"
}
if ($Kernel -ne "") {
$Arguments += " --kernelPriv"
}
if ($DuoNic) {
$Arguments += " --duoNic"
}
if ($UseQtip) {
$Arguments += " --useQTIP"
}
if ("" -ne $OsRunner) {
$Arguments += " --osRunner=$OsRunner"
}
if ($PfxPath -ne "") {
$Arguments += " -PfxPath:$PfxPath"
}
# Start the test process and return some information about the test case.
[pscustomobject]@{
Name = $Name
InstanceName = $InstanceName
LogDir = $LogDir
StartTime = Get-Date
Timestamp = (Get-Date -UFormat "%Y-%m-%dT%T")
ResultsPath = $FinalResultsPath
Process = (Start-TestExecutable $Arguments $LogDir)
}
}
# Uses CDB.exe to print the crashing callstack in the dump file.
function PrintDumpCallStack($DumpFile) {
$env:_NT_SYMBOL_PATH = Split-Path $Path
try {
if ($env:BUILD_BUILDNUMBER -ne $null) {
$env:PATH += ";c:\Program Files (x86)\Windows Kits\10\Debuggers\x64"
}
$Output = cdb.exe -z $File -c "kn;q" | Join-String -Separator "`n"
$Output = ($Output | Select-String -Pattern " # Child-SP(?s).*quit:").Matches[0].Groups[0].Value
Write-Host "=================================================================================="
Write-Host " $(Split-Path $DumpFile -Leaf)"
Write-Host "=================================================================================="
$Output -replace "quit:", "=================================================================================="
$Output | Out-File "$DumpFile.txt"
} catch {
# Silently fail
}
}
function PrintLldbCoreCallStack($CoreFile) {
try {
$Output = lldb $Path -c $CoreFile -b -o "`"bt all`""
Write-Host "=================================================================================="
Write-Host " $(Split-Path $CoreFile -Leaf)"
Write-Host "=================================================================================="
# Find line containing Current thread
$Found = $false
$LastThreadStart = 0
for ($i = 0; $i -lt $Output.Length; $i++) {
if ($Output[$i] -like "*stop reason =*") {
if ($Found) {
break
}
$LastThreadStart = $i
}
if ($Output[$i] -like "*quic_bugcheck*") {
$Found = $true
for ($j = $LastThreadStart; $j -lt $i; $j++) {
$Output[$j]
}
}
if ($Found) {
$Output[$i]
}
}
if (!$Found) {
$Output | Join-String -Separator "`n"
}
$Output | Join-String -Separator "`n" | Out-File "$CoreFile.txt"
} catch {
# Silently Fail
}
}
function PrintGdbCoreCallStack($CoreFile) {
try {
$Output = gdb $Path $CoreFile -batch -ex "`"bt`"" -ex "`"quit`""
Write-Host "=================================================================================="
Write-Host " $(Split-Path $CoreFile -Leaf)"
Write-Host "=================================================================================="
# Find line containing Current thread
$Found = $false
for ($i = 0; $i -lt $Output.Length; $i++) {
if ($Output[$i] -like "*Current thread*") {
$Found = $true
}
if ($Found) {
$Output[$i]
}
}
if (!$Found) {
$Output | Join-String -Separator "`n"
}
$Output | Join-String -Separator "`n" | Out-File "$CoreFile.txt"
} catch {
# Silently Fail
}
}
# Waits for the executable to finish and processes the results.
function Wait-TestCase($TestCase) {
$ProcessCrashed = $false
$AnyTestFailed = $false
$StdOut = $null
$StdOutTxt = $null
$StdError = $null
$StdErrorTxt = $null
$IsReadingStreams = $false
try {
if (!$Debugger) {
$IsReadingStreams = $true
$StdOut = $TestCase.Process.StandardOutput.ReadToEndAsync()
$StdError = $TestCase.Process.StandardError.ReadToEndAsync()
}
$TestCase.Process.WaitForExit()
if ($TestCase.Process.ExitCode -ne 0) {
Log "Process had nonzero exit code: $($TestCase.Process.ExitCode)"
$ProcessCrashed = $true
}
if ($IsReadingStreams) {
[System.Threading.Tasks.Task]::WaitAll(@($StdOut, $StdError))
$StdOutTxt = $StdOut.Result
$StdErrorTxt = $StdError.Result
if (!$IsWindows -and !$ProcessCrashed) {
$ProcessCrashed = $StdErrorTxt.Contains("Aborted")
}
$AnyTestFailed = $StdOutTxt.Contains("[ FAILED ]")
if (!(Test-Path $TestCase.ResultsPath) -and !$ProcessCrashed) {
LogWrn "No test results generated! Treating as crash!"
$ProcessCrashed = $true
}
}
$DumpFiles = (Get-ChildItem $TestCase.LogDir) | Where-Object { $_.Extension -eq ".dmp" }
if ($DumpFiles) {
LogWrn "Dump file(s) generated"
foreach ($File in $DumpFiles) {
PrintDumpCallStack($File)
}
$ProcessCrashed = $true
}
$CoreFiles = (Get-ChildItem $TestCase.LogDir) | Where-Object { $_.Extension -eq ".core" }
if ($CoreFiles) {
LogWrn "Core file(s) generated"
foreach ($File in $CoreFiles) {
if ($IsMacOS) {
PrintLldbCoreCallStack $File
} else {
PrintGdbCoreCallStack $File
}
}
$ProcessCrashed = $true
}
} catch {
LogWrn "Treating exception as crash!"
$ProcessCrashed = $true
throw
} finally {
# Add the current test case results.
if ($IsolationMode -ne "Batch") {
try { Add-XmlResults $TestCase } catch { }
}
if ($CodeCoverage) {
$NewCoverage = Join-Path $TestCase.LogDir $Coveragename
if ($IsolationMode -eq "Isolated") {
# Merge coverage with previous runs
$PreviousCoverage = Join-Path $CoverageDir $CoverageName
if (!(Test-Path $PreviousCoverage)) {
# No previous coverage data, just copy
Copy-Item $NewCoverage $CoverageDir
} else {
# Merge new coverage data with existing coverage data
# On a developer machine, this will always merge coverage until the dev deletes old coverage.
$TempMergedCoverage = Join-Path $CoverageDir "mergetemp.cov"
$CoverageExe = 'C:\"Program Files"\OpenCppCoverage\OpenCppCoverage.exe'
$CoverageMergeParams = " --input_coverage $($PreviousCoverage) --input_coverage $($NewCoverage) --export_type binary:$($TempMergedCoverage)"
Invoke-Expression ($CoverageExe + $CoverageMergeParams) | Out-Null
Move-Item $TempMergedCoverage $PreviousCoverage -Force
}
} else {
# Copy the coverage to destination
Copy-Item $NewCoverage $CoverageDir -Force
# Copy coverage log
$LogName = "LastCoverageResults-$(Split-Path $Path -LeafBase).log"
Copy-Item (Join-Path $TestCase.LogDir "LastCoverageResults.log") (Join-Path $CoverageDir $LogName) -Force
}
}
if ($ProcessCrashed) {
$global:CrashedProcessCount++
}
if ($IsolationMode -eq "Batch") {
if ($StdOutTxt) { Write-Host $StdOutTxt }
if ($StdErrorTxt) { Write-Host $StdErrorTxt }
} else {
$Delta = (Get-Date) - $TestCase.StartTime
if ($AnyTestFailed -or $ProcessCrashed) {
LogErr "$($TestCase.Name) failed (in $($Delta.TotalSeconds) sec):"
if ($StdOutTxt) { Write-Host $StdOutTxt }
if ($StdErrorTxt) { Write-Host $StdErrorTxt }
} else {
Log "$($TestCase.Name) succeeded (in $($Delta.TotalSeconds) sec)"
}
}
if ($KeepOutputOnSuccess -or $ProcessCrashed -or $AnyTestFailed) {
if ($LogProfile -ne "None") {
& $LogScript -Stop -OutputPath (Join-Path $TestCase.LogDir "quic")
}
if ($StdOutTxt) {
$StdOutTxt > (Join-Path $TestCase.LogDir "stdout.txt")
}
if ($StdErrorTxt) {
$StdErrorTxt > (Join-Path $TestCase.LogDir "stderr.txt")
}
if ($CompressOutput) {
# Zip the output.
CompressOutput-Archive -Path "$($TestCase.LogDir)\*" -DestinationPath "$($TestCase.LogDir).zip" | Out-Null
Remove-Item $TestCase.LogDir -Recurse -Force | Out-Null
}
} else {
if ($LogProfile -ne "None") {
& $LogScript -Cancel | Out-Null
}
Remove-Item $TestCase.LogDir -Recurse -Force | Out-Null
}
}
}
# Runs the test executable to query all available test cases, parses the console
# output and returns a list of test case names.
function GetTestCases {
$Arguments = " --gtest_list_tests"
if ($Filter -ne "") {
$Arguments = " --gtest_filter=$Filter --gtest_list_tests"
}
$stdout = Invoke-Expression ($Path + $Arguments)
$Tests = New-Object System.Collections.ArrayList
if ($null -ne $stdout) {
$Lines = ($stdout.Split([Environment]::NewLine)) | Where-Object { $_.Length -ne 0 }
$CurTestGroup = $null
for ($i = 0; $i -lt $Lines.Length; $i++) {
if (!($Lines[$i].StartsWith(" "))) {
$CurTestGroup = $Lines[$i]
} else {
$Tests.Add($CurTestGroup + $Lines[$i].Split("#")[0].Trim()) | Out-Null
}
}
}
$Tests.ToArray()
}
function Get-WindowsKitTool {
param (
[string]$Arch = "x86",
[Parameter(Mandatory = $true)]
[string]$Tool
)
$KitBinRoot = "C:\Program Files (x86)\Windows Kits\10\bin"
if (!(Test-Path $KitBinRoot)) {
Write-Error "Windows Kit Binary Folder not Found"
return ""
}
$FoundToolPath = $null
$FoundToolVersion = "0"
$Subfolders = Get-ChildItem -Path $KitBinRoot -Directory
foreach ($Subfolder in $Subfolders) {
$ToolPath = Join-Path $Subfolder "$Arch\$Tool"
if (Test-Path $ToolPath) {
$KitVersion = $Subfolder.Name
if ($KitVersion -gt $FoundToolVersion) {
$FoundToolVersion = $KitVersion
$FoundToolPath = $ToolPath
}
}
}
if ($null -ne $FoundToolPath) {
return $FoundToolPath
}
Write-Error "Failed to find tool"
return $null
}
##############################################################
# Main Execution #
##############################################################
# Query all the test cases.
$TestCases = GetTestCases
if ($null -eq $TestCases) {
Log "$Path (Skipped)"
exit
}
$TestCount = ($TestCases -as [String[]]).Length
Log "$Path ($TestCount test case(s))"
if ($ListTestCases) {
# List the tst cases.
$TestCases
exit
}
# Cancel any outstanding logs that might be leftover.
& $LogScript -Cancel | Out-Null
# Initialize WER dump registry key if necessary.
if ($IsWindows -and !(Test-Path $WerDumpRegPath) -and (Test-Administrator)) {
New-Item -Path $WerDumpRegPath -Force | Out-Null
}
# Initialize application verifier (Windows only).
if ($IsWindows -and $EnableAppVerifier) {
where.exe appverif.exe
if ($LastExitCode -eq 0) {
appverif.exe /verify $Path
} else {
Write-Warning "Application Verifier not installed!"
$EnableAppVerifier = $false;
}
}
$DriverPath = (Split-Path $Path -Parent)
# Install the kernel mode drivers.
if ($Kernel -ne "") {
if ($null -ne (Get-Service -Name "msquicpriv" -ErrorAction Ignore)) {
try { net.exe stop msquicpriv /y | Out-Null } catch {}
sc.exe delete msquicpriv /y | Out-Null
}
if ($null -ne (Get-Service -Name "msquictestpriv" -ErrorAction Ignore)) {
try { net.exe stop msquictestpriv /y | Out-Null } catch {}
sc.exe delete msquictestpriv /y | Out-Null
}
Copy-Item (Join-Path $Kernel "msquictestpriv.sys") $DriverPath -Force
Copy-Item (Join-Path $Kernel "msquicpriv.sys") $DriverPath -Force
Log "Creating msquicpriv service"
sc.exe create "msquicpriv" type= kernel binpath= (Join-Path $DriverPath msquicpriv.sys) start= demand | Out-Null
if ($LastExitCode) {
Log ("sc.exe " + $LastExitCode)
}
if ($EnableSystemVerifier) {
verifier.exe /volatile /adddriver msquicpriv.sys msquictestpriv.sys /flags 0x9BB
if ($LastExitCode) {
Log ("verifier.exe " + $LastExitCode)
}
}
Log "Starting msquicpriv service"
sc.exe start msquicpriv
if ($LastExitCode) {
Log ("net.exe " + $LastExitCode)
}
try {
if ("Running" -ne (Get-Service -Name msquicpriv).Status) {
LogFatal "msquicpriv isn't running"
}
} catch {
LogFatal "msquicpriv query failed"
}
}
if ($IsWindows -and $EnableSystemVerifier) {
verifier.exe /volatile /adddriver afd.sys netio.sys tcpip.sys /flags 0x9BB
if ($LastExitCode) {
Log ("verifier.exe " + $LastExitCode)
}
}
try {
if ($IsolationMode -eq "Batch") {
# Run the the test process once for all tests.
Wait-TestCase (Start-AllTestCases)
} else {
# Run the test cases individually.
for ($i = 0; $i -lt $TestCount; $i++) {
Wait-TestCase (Start-TestCase ($TestCases -as [String[]])[$i])
if (!$NoProgress) {
Write-Progress -Activity "Running tests" -Status "Progress:" -PercentComplete ($i/$TestCount*100)
}
}
}
} catch {
Log "Exception Thrown"
Log $_
Get-Error
$_ | Format-List *
} finally {
if ($LogProfile -ne "None") {
& $LogScript -Cancel | Out-Null
}
if ($IsWindows) {
# Cleanup the WER registry.
if (Test-Administrator) {
Remove-Item -Path $WerDumpRegPath -Force | Out-Null
}
# Turn off App Verifier
if ($EnableAppVerifier) {
appverif.exe -disable * -for $Path
}
}
if ($IsolationMode -eq "Batch") {
if (Test-Path $FinalResultsPath) {
$XmlResults = [xml](Get-Content $FinalResultsPath)
if (!$GenerateXmlResults) {
# Delete the XML results file since it's not needed.
Remove-Item $FinalResultsPath -Force | Out-Null
}
} else {
# No results file means the tests crashed most likely.
$NewXmlText = $FailXmlText.Replace("TestSuiteName", "all")
$NewXmlText = $NewXmlText.Replace("TestCaseName", "all")
$NewXmlText = $NewXmlText.Replace("date", $XmlResults.testsuites.timestamp)
$XmlResults = [xml]($NewXmlText)
if ($GenerateXmlResults) {
# Save the xml results.
$XmlResults.Save($FinalResultsPath) | Out-Null
}
}
} else {
if ($GenerateXmlResults) {
# Save the xml results.
$XmlResults.Save($FinalResultsPath) | Out-Null
}
}
$TestCount = $XmlResults.testsuites.tests -as [Int]
$TestsFailed = $XmlResults.testsuites.failures -as [Int]
# Uninstall the kernel mode test driver and revert the msquic driver.
if ($Kernel -ne "") {
net.exe stop msquicpriv /y | Out-Null
sc.exe delete msquictestpriv | Out-Null
sc.exe delete msquicpriv | Out-Null
Remove-Item (Join-Path $DriverPath msquicpriv.sys) -Force
Remove-Item (Join-Path $DriverPath msquictestpriv.sys) -Force
}
if ($IsWindows -and $EnableSystemVerifier) {
if ($Kernel -ne "") {
verifier.exe /volatile /removedriver msquicpriv.sys msquictestpriv.sys
verifier.exe /volatile /flags 0x0
}
verifier.exe /volatile /removedriver afd.sys netio.sys tcpip.sys
verifier.exe /volatile /flags 0x0
}
# Print out the results.
Log "$($TestCount) test(s) run."
if ($KeepOutputOnSuccess -or ($TestsFailed -ne 0) -or ($global:CrashedProcessCount -ne 0)) {
Log "Output can be found in $($LogDir)"
if ($ErrorsAsWarnings) {
Write-Warning "$($TestsFailed) test(s) failed."
Write-Warning "$($TestsFailed) test(s) failed, $($global:CrashedProcessCount) test(s) crashed."
} else {
Write-Error "$($TestsFailed) test(s) failed, $($global:CrashedProcessCount) test(s) crashed."
$LastExitCode = 1
}
} elseif ($AZP -and $TestCount -eq 0) {
Write-Error "Failed to run any tests."
} else {
if (Test-Path $LogDir) {
Remove-Item $LogDir -Recurse -Force | Out-Null
}
}
}