mirror of
https://github.com/eclipse-threadx/filex.git
synced 2026-09-14 04:05:59 +08:00
- New port: ports/win64/vs_2022/inc/fx_port.h
- Based on the Win32 port; updated ALIGN_TYPE to ULONG64 for 64-bit
pointers, added FX_REGRESSION_TEST hooks from the Linux port, and
bumped the port name string to Win64/Visual.
- New CMake infrastructure: ports/win64/vs_2022/CMakeLists.txt
- Exposes only the inc/ directory (port is header-only).
- Updated test/cmake/CMakeLists.txt
- Added MSVC guards: skip -m32 / GCC coverage flags; locate VS 2022
toolchain; build ThreadX from source alongside FileX via
THREADX_SOURCE_DIR.
- Updated test/cmake/regression/CMakeLists.txt
- Added MSVC-compatible compile options (/W3 /Zi instead of -Wall
-fprofile-arcs); added win64_compat include path for standalone +
MSVC builds; added per-test TIMEOUT overrides for the two inherently
long-running fault-tolerant tests on Windows.
- Updated test/cmake/samples/CMakeLists.txt
- Added MSVC-compatible flag handling.
- New PowerShell scripts: scripts/build_fx.ps1, scripts/test_fx.ps1,
scripts/fx_windows_common.ps1
- Support all 9 build configurations; -Clean, -Config, -Verbose flags;
modelled on the ThreadX Win64 build/test scripts.
- New Windows compatibility shims for standalone builds:
test/regression_test/win64_compat/pthread.h
test/regression_test/win64_compat/unistd.h
- Minimal pthreads (CreateThread / WaitForSingleObject / TerminateThread)
and usleep (Sleep) shims so standalone test files compile on MSVC.
- Fixed test/regression_test/fx_ram_driver_test.c and .h
- RAM driver: added bounds check so out-of-range sector READs use a
safe scratch buffer instead of segfaulting.
- MSVC: large test buffers (ram_disk_memory_large, large_data_buffer,
and standalone-mode ram_disk_memory / ram_disk_memory1) moved from
BSS to calloc() via a .CRT\ constructor. This avoids the MSVC
PE image 2 GB hard limit (LNK1248) and eliminates demand-zero page-
fault overhead that would otherwise slow the test suite.
- Bulk-fixed 93 fault-tolerant test files
- [FAULT_TOLERANT_SIZE] -> [FAULT_TOLERANT_SIZE > 0 ? FAULT_TOLERANT_SIZE : 1]
so that MSVC C2466 (zero-length array) is not triggered when
FX_ENABLE_FAULT_TOLERANT is not defined.
- Fixed filextestcontrol.c: standalone test runner thread exit
- The Win64 pthread shim makes pthread_exit() a no-op so that
test_control_thread_entry() proceeds to exit(failed_tests),
giving ctest the correct process exit code.
- Fixed common/src/fx_utility_logical_sector_write.c
- Added missing bounds guard to prevent sector-write past end of
RAM disk during fault-tolerant interrupt simulation tests.
All 9 build configurations pass 136/136 regression tests on Win64/VS 2022.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
940 lines
27 KiB
PowerShell
940 lines
27 KiB
PowerShell
[CmdletBinding()]
|
|
param()
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Invoke-NativeCommand {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$FilePath,
|
|
|
|
[Parameter()]
|
|
[string[]]$Arguments = @()
|
|
)
|
|
|
|
& $FilePath @Arguments
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $($Arguments -join ' ')"
|
|
}
|
|
}
|
|
|
|
function Get-CommandPathIfAvailable {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$CommandName
|
|
)
|
|
|
|
$command = Get-Command $CommandName -ErrorAction SilentlyContinue
|
|
if ($null -eq $command) {
|
|
return $null
|
|
}
|
|
|
|
return $command.Source
|
|
}
|
|
|
|
function Get-PortSettings {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$SelectedArch
|
|
)
|
|
|
|
switch ($SelectedArch) {
|
|
'win32' {
|
|
return @{
|
|
FileXArch = 'win32'
|
|
FileXToolchain = 'vs_2019'
|
|
VsArch = 'x86'
|
|
}
|
|
}
|
|
'win64' {
|
|
return @{
|
|
FileXArch = 'win64'
|
|
FileXToolchain = 'vs_2022'
|
|
VsArch = 'amd64'
|
|
}
|
|
}
|
|
default {
|
|
throw "Unsupported architecture: $SelectedArch"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-RegressionConfigurations {
|
|
return @(
|
|
'default_build_coverage',
|
|
'no_cache_build',
|
|
'no_cache_standalone_build',
|
|
'fault_tolerant_build_coverage',
|
|
'no_check_build',
|
|
'no_cache_fault_tolerant_build',
|
|
'standalone_build_coverage',
|
|
'standalone_fault_tolerant_build_coverage',
|
|
'standalone_no_cache_fault_tolerant_build'
|
|
)
|
|
}
|
|
|
|
function Resolve-RegressionConfigurations {
|
|
param(
|
|
[Parameter(Mandatory = $false)]
|
|
[AllowNull()]
|
|
[AllowEmptyCollection()]
|
|
[object]$RequestedConfigurations = 'all'
|
|
)
|
|
|
|
$allConfigurations = Get-RegressionConfigurations
|
|
$resolvedConfigurations = @()
|
|
|
|
if ($null -eq $RequestedConfigurations) {
|
|
$resolvedConfigurations = @('all')
|
|
}
|
|
elseif ($RequestedConfigurations -is [System.Array]) {
|
|
foreach ($requestedConfiguration in $RequestedConfigurations) {
|
|
if ($null -ne $requestedConfiguration) {
|
|
$resolvedConfigurations += [string]$requestedConfiguration
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$resolvedConfigurations = @([string]$RequestedConfigurations)
|
|
}
|
|
|
|
$normalizedConfigurations = @()
|
|
foreach ($requestedConfiguration in $resolvedConfigurations) {
|
|
foreach ($configurationPart in ($requestedConfiguration -split ',')) {
|
|
$trimmedConfiguration = $configurationPart.Trim()
|
|
if ($trimmedConfiguration.Length -gt 0) {
|
|
$normalizedConfigurations += $trimmedConfiguration
|
|
}
|
|
}
|
|
}
|
|
|
|
if (($normalizedConfigurations.Count -eq 0) -or ($normalizedConfigurations -contains 'all')) {
|
|
return $allConfigurations
|
|
}
|
|
|
|
foreach ($normalizedConfiguration in $normalizedConfigurations) {
|
|
if ($allConfigurations -notcontains $normalizedConfiguration) {
|
|
throw "Unsupported configuration: $normalizedConfiguration"
|
|
}
|
|
}
|
|
|
|
return $normalizedConfigurations
|
|
}
|
|
|
|
function Get-RegressionBuildDirectoryName {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ConfigurationName
|
|
)
|
|
|
|
switch ($ConfigurationName) {
|
|
'default_build_coverage' { return 'dbc' }
|
|
'no_cache_build' { return 'nc' }
|
|
'no_cache_standalone_build' { return 'ncs' }
|
|
'fault_tolerant_build_coverage' { return 'ft' }
|
|
'no_check_build' { return 'nck' }
|
|
'no_cache_fault_tolerant_build' { return 'ncft' }
|
|
'standalone_build_coverage' { return 'sa' }
|
|
'standalone_fault_tolerant_build_coverage' { return 'saft' }
|
|
'standalone_no_cache_fault_tolerant_build' { return 'sancft' }
|
|
default {
|
|
throw "Unsupported configuration: $ConfigurationName"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Enter-VisualStudioDevShell {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$VsArch
|
|
)
|
|
|
|
$targetArch = switch ($VsArch) {
|
|
'amd64' { 'x64' }
|
|
'x86' { 'x86' }
|
|
default { $VsArch }
|
|
}
|
|
|
|
if ((Get-Command cl -ErrorAction SilentlyContinue) -and ($env:VSCMD_ARG_TGT_ARCH -eq $targetArch)) {
|
|
return
|
|
}
|
|
|
|
$vsWherePath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
|
|
if (-not (Test-Path -LiteralPath $vsWherePath)) {
|
|
throw "Unable to locate vswhere.exe at $vsWherePath"
|
|
}
|
|
|
|
$installationPath = & $vsWherePath -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
|
|
if (-not $installationPath) {
|
|
throw 'Unable to locate a Visual Studio 2022 installation with MSVC build tools.'
|
|
}
|
|
|
|
$launchScript = Join-Path $installationPath 'Common7\Tools\Launch-VsDevShell.ps1'
|
|
if (-not (Test-Path -LiteralPath $launchScript)) {
|
|
throw "Unable to locate Launch-VsDevShell.ps1 at $launchScript"
|
|
}
|
|
|
|
$env:VSCMD_SKIP_SENDTELEMETRY = '1'
|
|
& $launchScript -VsInstallationPath $installationPath -Arch $VsArch -HostArch amd64 -SkipAutomaticLocation | Out-Null
|
|
|
|
if (-not (Get-Command cl -ErrorAction SilentlyContinue)) {
|
|
throw 'MSVC compiler environment was not activated successfully.'
|
|
}
|
|
}
|
|
|
|
function Remove-BuildDirectory {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Path,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$RepoRoot
|
|
)
|
|
|
|
$fullRepoRoot = [System.IO.Path]::GetFullPath($RepoRoot)
|
|
$fullPath = [System.IO.Path]::GetFullPath($Path)
|
|
|
|
if (-not $fullPath.StartsWith($fullRepoRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "Refusing to remove a directory outside the repository: $fullPath"
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $fullPath) {
|
|
try {
|
|
Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction Stop
|
|
return
|
|
} catch {
|
|
Write-Warning "Failed to remove build directory '$fullPath': $($_.Exception.Message)"
|
|
}
|
|
|
|
Get-ChildItem -LiteralPath $fullPath -Force -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
|
|
try {
|
|
if (($_.Attributes -band [System.IO.FileAttributes]::ReadOnly) -ne 0) {
|
|
$_.Attributes = ($_.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly))
|
|
}
|
|
} catch {
|
|
}
|
|
}
|
|
|
|
try {
|
|
Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction Stop
|
|
} catch {
|
|
Write-Warning "Proceeding with partially cleaned build directory '$fullPath': $($_.Exception.Message)"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Remove-CtestTestingDirectory {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Path
|
|
)
|
|
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
|
|
return
|
|
} catch {
|
|
Write-Warning "Failed to remove CTest directory '$Path': $($_.Exception.Message)"
|
|
}
|
|
|
|
Get-ChildItem -LiteralPath $Path -Force -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
|
|
try {
|
|
if (($_.Attributes -band [System.IO.FileAttributes]::ReadOnly) -ne 0) {
|
|
$_.Attributes = ($_.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly))
|
|
}
|
|
} catch {
|
|
}
|
|
}
|
|
|
|
try {
|
|
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
|
|
} catch {
|
|
Write-Warning "Proceeding with partially cleaned CTest directory '$Path': $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function Remove-NinjaLock {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Path
|
|
)
|
|
|
|
$ninjaLockPath = Join-Path $Path '.ninja_lock'
|
|
if (Test-Path -LiteralPath $ninjaLockPath) {
|
|
Remove-Item -LiteralPath $ninjaLockPath -Force
|
|
}
|
|
}
|
|
|
|
function Get-WindowsDebuggerPath {
|
|
$debuggerPath = Get-CommandPathIfAvailable -CommandName 'cdb.exe'
|
|
if ($debuggerPath) {
|
|
return $debuggerPath
|
|
}
|
|
|
|
$candidatePaths = @(
|
|
(Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Debuggers\x64\cdb.exe'),
|
|
(Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\Debuggers\x86\cdb.exe')
|
|
)
|
|
|
|
foreach ($candidatePath in $candidatePaths) {
|
|
if (Test-Path -LiteralPath $candidatePath) {
|
|
return $candidatePath
|
|
}
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Get-SanitizedFileName {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Name
|
|
)
|
|
|
|
$safeName = [regex]::Replace($Name, '[<>:"/\\|?*]', '_')
|
|
$safeName = $safeName -replace '\s+', '_'
|
|
return $safeName
|
|
}
|
|
|
|
function Initialize-MinidumpSupport {
|
|
if ($null -ne ('FileX.WindowsMiniDump' -as [type])) {
|
|
return
|
|
}
|
|
|
|
Add-Type -TypeDefinition @'
|
|
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace FileX
|
|
{
|
|
public static class WindowsMiniDump
|
|
{
|
|
[DllImport("Dbghelp.dll", SetLastError = true)]
|
|
private static extern bool MiniDumpWriteDump(
|
|
IntPtr hProcess,
|
|
uint processId,
|
|
IntPtr hFile,
|
|
uint dumpType,
|
|
IntPtr exceptionParam,
|
|
IntPtr userStreamParam,
|
|
IntPtr callbackParam);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern IntPtr OpenProcess(uint desiredAccess, bool inheritHandle, int processId);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool CloseHandle(IntPtr handle);
|
|
|
|
private const uint ProcessQueryInformation = 0x0400U;
|
|
private const uint ProcessVmRead = 0x0010U;
|
|
private const uint ProcessDupHandle = 0x0040U;
|
|
|
|
public static bool WriteDump(int processId, string dumpPath, uint dumpType, out int errorCode)
|
|
{
|
|
IntPtr processHandle = OpenProcess(ProcessQueryInformation | ProcessVmRead | ProcessDupHandle, false, processId);
|
|
if (processHandle == IntPtr.Zero)
|
|
{
|
|
errorCode = Marshal.GetLastWin32Error();
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
using (FileStream dumpStream = new FileStream(dumpPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
|
|
{
|
|
bool success = MiniDumpWriteDump(
|
|
processHandle,
|
|
unchecked((uint)processId),
|
|
dumpStream.SafeFileHandle.DangerousGetHandle(),
|
|
dumpType,
|
|
IntPtr.Zero,
|
|
IntPtr.Zero,
|
|
IntPtr.Zero);
|
|
|
|
errorCode = success ? 0 : Marshal.GetLastWin32Error();
|
|
return success;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
CloseHandle(processHandle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
'@
|
|
}
|
|
|
|
function Wait-FileReadable {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Path,
|
|
|
|
[Parameter()]
|
|
[int]$TimeoutSeconds = 10
|
|
)
|
|
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
while ((Get-Date) -lt $deadline) {
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
Start-Sleep -Milliseconds 200
|
|
continue
|
|
}
|
|
|
|
try {
|
|
$fileStream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
|
$fileStream.Dispose()
|
|
return $true
|
|
}
|
|
catch {
|
|
Start-Sleep -Milliseconds 200
|
|
}
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Get-CtestTestMetadata {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir
|
|
)
|
|
|
|
$ctestOutput = & ctest --test-dir $BuildDir --show-only=json-v1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Unable to enumerate ctest metadata in $BuildDir"
|
|
}
|
|
|
|
return (($ctestOutput -join [Environment]::NewLine) | ConvertFrom-Json).tests
|
|
}
|
|
|
|
function Get-CtestFailedTestNames {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$TestingTemporaryDir
|
|
)
|
|
|
|
$lastFailedPath = Join-Path $TestingTemporaryDir 'LastTestsFailed.log'
|
|
if (-not (Test-Path -LiteralPath $lastFailedPath)) {
|
|
return @()
|
|
}
|
|
|
|
$failedTestNames = @()
|
|
foreach ($logLine in (Get-Content -LiteralPath $lastFailedPath)) {
|
|
if ([string]::IsNullOrWhiteSpace($logLine)) {
|
|
continue
|
|
}
|
|
|
|
if ($logLine -match '^\s*\d+:(?<name>.+)\s*$') {
|
|
$failedTestNames += $Matches['name'].Trim()
|
|
}
|
|
else {
|
|
$failedTestNames += $logLine.Trim()
|
|
}
|
|
}
|
|
|
|
return $failedTestNames
|
|
}
|
|
|
|
function Invoke-ProcessDumpCapture {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$ProcessId,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$DumpPath,
|
|
|
|
[Parameter()]
|
|
[int]$TimeoutSeconds = 15
|
|
)
|
|
|
|
$outputDirectory = Split-Path -Parent $DumpPath
|
|
if (-not (Test-Path -LiteralPath $outputDirectory)) {
|
|
New-Item -ItemType Directory -Path $outputDirectory | Out-Null
|
|
}
|
|
|
|
Remove-Item -LiteralPath $DumpPath -Force -ErrorAction SilentlyContinue
|
|
|
|
Initialize-MinidumpSupport
|
|
$dumpType = [uint32]0x00001006
|
|
$errorCode = 0
|
|
$dumpCaptured = [FileX.WindowsMiniDump]::WriteDump($ProcessId, $DumpPath, $dumpType, [ref]$errorCode)
|
|
|
|
if (-not $dumpCaptured) {
|
|
Write-Warning "MiniDumpWriteDump failed for PID ${ProcessId} with Win32 error $errorCode"
|
|
return $false
|
|
}
|
|
|
|
return (Test-Path -LiteralPath $DumpPath)
|
|
}
|
|
|
|
function Invoke-DumpStackAnalysis {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$DumpPath,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$OutputBasePath,
|
|
|
|
[Parameter()]
|
|
[string]$SymbolPath,
|
|
|
|
[Parameter()]
|
|
[int]$TimeoutSeconds = 15
|
|
)
|
|
|
|
if (-not (Test-Path -LiteralPath $DumpPath)) {
|
|
Write-Warning "Skipping dump analysis because the dump file was not created: $DumpPath"
|
|
return $false
|
|
}
|
|
|
|
if (-not (Wait-FileReadable -Path $DumpPath)) {
|
|
Write-Warning "Skipping dump analysis because the dump file is not readable yet: $DumpPath"
|
|
return $false
|
|
}
|
|
|
|
$debuggerPath = Get-WindowsDebuggerPath
|
|
if (-not $debuggerPath) {
|
|
Write-Warning 'Skipping dump analysis because cdb.exe is not available.'
|
|
return $false
|
|
}
|
|
|
|
$outputDirectory = Split-Path -Parent $OutputBasePath
|
|
if (-not (Test-Path -LiteralPath $outputDirectory)) {
|
|
New-Item -ItemType Directory -Path $outputDirectory | Out-Null
|
|
}
|
|
|
|
$stdoutPath = "${OutputBasePath}.stdout.txt"
|
|
$stderrPath = "${OutputBasePath}.stderr.txt"
|
|
$commandFilePath = "${OutputBasePath}.commands.txt"
|
|
Set-Content -LiteralPath $commandFilePath -Value @(
|
|
'!runaway 7'
|
|
'~* kb 200'
|
|
'q'
|
|
) -Encoding Ascii
|
|
$cdbArguments = @(
|
|
'-lines',
|
|
'-z', $DumpPath
|
|
)
|
|
|
|
if ($SymbolPath) {
|
|
$cdbArguments += @('-y', $SymbolPath)
|
|
}
|
|
|
|
$cdbArguments += @('-cf', $commandFilePath)
|
|
$cdbProcess = Start-Process -FilePath $debuggerPath -ArgumentList $cdbArguments -PassThru -NoNewWindow `
|
|
-RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath
|
|
|
|
try {
|
|
$cdbProcess | Wait-Process -Timeout $TimeoutSeconds -ErrorAction Stop
|
|
}
|
|
catch {
|
|
if (-not $cdbProcess.HasExited) {
|
|
$null = Start-Process -FilePath 'taskkill.exe' -ArgumentList @('/PID', $cdbProcess.Id.ToString(), '/T', '/F') `
|
|
-WindowStyle Hidden -Wait -PassThru
|
|
}
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function Invoke-ProcessWithTimeout {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$FilePath,
|
|
|
|
[Parameter()]
|
|
[string[]]$Arguments = @(),
|
|
|
|
[Parameter()]
|
|
[int]$TimeoutSeconds = 0,
|
|
|
|
[Parameter()]
|
|
[string]$WorkingDirectory,
|
|
|
|
[Parameter()]
|
|
[string]$RedirectStandardOutputPath,
|
|
|
|
[Parameter()]
|
|
[string]$RedirectStandardErrorPath,
|
|
|
|
[Parameter()]
|
|
[scriptblock]$OnTimeout,
|
|
|
|
[Parameter()]
|
|
[scriptblock]$PostTimeout
|
|
)
|
|
|
|
$argumentList = @()
|
|
foreach ($argument in $Arguments) {
|
|
if ($argument -match '\s|"') {
|
|
$argumentList += '"' + ($argument -replace '"', '\"') + '"'
|
|
}
|
|
else {
|
|
$argumentList += $argument
|
|
}
|
|
}
|
|
|
|
$startProcessParameters = @{
|
|
FilePath = $FilePath
|
|
NoNewWindow = $true
|
|
PassThru = $true
|
|
}
|
|
|
|
if ($argumentList.Count -gt 0) {
|
|
$startProcessParameters['ArgumentList'] = $argumentList
|
|
}
|
|
|
|
if ($WorkingDirectory) {
|
|
$startProcessParameters['WorkingDirectory'] = $WorkingDirectory
|
|
}
|
|
|
|
if ($RedirectStandardOutputPath) {
|
|
$startProcessParameters['RedirectStandardOutput'] = $RedirectStandardOutputPath
|
|
}
|
|
|
|
if ($RedirectStandardErrorPath) {
|
|
$startProcessParameters['RedirectStandardError'] = $RedirectStandardErrorPath
|
|
}
|
|
|
|
$process = Start-Process @startProcessParameters
|
|
if ($TimeoutSeconds -le 0) {
|
|
$process | Wait-Process
|
|
$completed = $true
|
|
}
|
|
else {
|
|
try {
|
|
$process | Wait-Process -Timeout $TimeoutSeconds -ErrorAction Stop
|
|
$completed = $true
|
|
}
|
|
catch {
|
|
$completed = $false
|
|
}
|
|
}
|
|
|
|
if (-not $completed) {
|
|
if ($null -ne $OnTimeout) {
|
|
& $OnTimeout $process
|
|
}
|
|
|
|
$null = Start-Process -FilePath 'taskkill.exe' -ArgumentList @('/PID', $process.Id.ToString(), '/T', '/F') -WindowStyle Hidden -Wait -PassThru
|
|
|
|
if ($null -ne $PostTimeout) {
|
|
& $PostTimeout $process
|
|
}
|
|
|
|
return @{
|
|
Completed = $false
|
|
ExitCode = $null
|
|
ProcessId = $process.Id
|
|
}
|
|
}
|
|
|
|
$process.Refresh()
|
|
return @{
|
|
Completed = $true
|
|
ExitCode = $process.ExitCode
|
|
ProcessId = $process.Id
|
|
}
|
|
}
|
|
|
|
function Invoke-CtestFailureDiagnostics {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$TestingTemporaryDir,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$TimeoutSeconds
|
|
)
|
|
|
|
$failedTestNames = Get-CtestFailedTestNames -TestingTemporaryDir $TestingTemporaryDir
|
|
if ($failedTestNames.Count -eq 0) {
|
|
Write-Warning "No failed tests were recorded in $TestingTemporaryDir"
|
|
return
|
|
}
|
|
|
|
$testMetadataList = Get-CtestTestMetadata -BuildDir $BuildDir
|
|
$testMetadataMap = @{}
|
|
foreach ($testMetadata in $testMetadataList) {
|
|
$testMetadataMap[$testMetadata.name] = $testMetadata
|
|
}
|
|
|
|
$diagnosticsRoot = Join-Path $TestingTemporaryDir 'FailureDiagnostics'
|
|
if (-not (Test-Path -LiteralPath $diagnosticsRoot)) {
|
|
New-Item -ItemType Directory -Path $diagnosticsRoot | Out-Null
|
|
}
|
|
|
|
foreach ($failedTestName in $failedTestNames) {
|
|
if (-not $testMetadataMap.ContainsKey($failedTestName)) {
|
|
Write-Warning "Unable to locate ctest metadata for failed test: $failedTestName"
|
|
continue
|
|
}
|
|
|
|
$testMetadata = $testMetadataMap[$failedTestName]
|
|
if (($null -eq $testMetadata.command) -or ($testMetadata.command.Count -eq 0)) {
|
|
Write-Warning "No executable command was recorded for failed test: $failedTestName"
|
|
continue
|
|
}
|
|
|
|
$testArguments = @()
|
|
if ($testMetadata.command.Count -gt 1) {
|
|
$testArguments = @($testMetadata.command[1..($testMetadata.command.Count - 1)])
|
|
}
|
|
|
|
$safeTestName = Get-SanitizedFileName -Name $failedTestName
|
|
$stdoutPath = Join-Path $diagnosticsRoot "${safeTestName}.stdout.txt"
|
|
$stderrPath = Join-Path $diagnosticsRoot "${safeTestName}.stderr.txt"
|
|
$debugOutputBasePath = Join-Path $diagnosticsRoot "${safeTestName}.cdb"
|
|
$workingDirectory = $null
|
|
$symbolDirectory = Split-Path -Parent $testMetadata.command[0]
|
|
|
|
if ($null -ne $testMetadata.properties) {
|
|
foreach ($testProperty in $testMetadata.properties) {
|
|
if ($testProperty.name -eq 'WORKING_DIRECTORY') {
|
|
$workingDirectory = $testProperty.value
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Warning "Collecting failure diagnostics for $failedTestName"
|
|
$dumpPath = '{0}.{1}.dmp' -f $debugOutputBasePath, ([DateTime]::UtcNow.ToString('yyyyMMddHHmmssfff'))
|
|
$testResult = Invoke-ProcessWithTimeout -FilePath $testMetadata.command[0] -Arguments $testArguments `
|
|
-TimeoutSeconds $TimeoutSeconds -WorkingDirectory $workingDirectory -RedirectStandardOutputPath $stdoutPath `
|
|
-RedirectStandardErrorPath $stderrPath -OnTimeout {
|
|
param($timedOutProcess)
|
|
Invoke-ProcessDumpCapture -ProcessId $timedOutProcess.Id -DumpPath $dumpPath | Out-Null
|
|
} -PostTimeout {
|
|
param($timedOutProcess)
|
|
if (Test-Path -LiteralPath $dumpPath) {
|
|
Invoke-DumpStackAnalysis -DumpPath $dumpPath -OutputBasePath $debugOutputBasePath -SymbolPath $symbolDirectory | Out-Null
|
|
}
|
|
}
|
|
|
|
if (-not $testResult.Completed) {
|
|
Write-Warning "Timeout diagnostics were captured for $failedTestName under $diagnosticsRoot"
|
|
continue
|
|
}
|
|
|
|
Write-Warning "Replay finished for $failedTestName with exit code $($testResult.ExitCode). Output was saved under $diagnosticsRoot"
|
|
}
|
|
}
|
|
|
|
function Test-IsNinjaBuildDirectory {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir
|
|
)
|
|
|
|
return (Test-Path -LiteralPath (Join-Path $BuildDir 'build.ninja'))
|
|
}
|
|
|
|
function Get-NinjaBuildStatements {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir
|
|
)
|
|
|
|
$buildNinjaPath = Join-Path $BuildDir 'build.ninja'
|
|
if (-not (Test-Path -LiteralPath $buildNinjaPath)) {
|
|
throw "Unable to locate build.ninja in $BuildDir"
|
|
}
|
|
|
|
return Get-Content -LiteralPath $buildNinjaPath
|
|
}
|
|
|
|
function New-NinjaRspFile {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$RspRelativePath
|
|
)
|
|
|
|
$buildStatements = Get-NinjaBuildStatements -BuildDir $BuildDir
|
|
$rspLine = ' RSP_FILE = ' + $RspRelativePath
|
|
$rspIndex = -1
|
|
|
|
for ($index = 0; $index -lt $buildStatements.Count; $index++) {
|
|
if ($buildStatements[$index] -eq $rspLine) {
|
|
$rspIndex = $index
|
|
break
|
|
}
|
|
}
|
|
|
|
if ($rspIndex -lt 0) {
|
|
throw "Unable to locate RSP_FILE entry for $RspRelativePath in build.ninja."
|
|
}
|
|
|
|
$buildIndex = -1
|
|
for ($index = $rspIndex; $index -ge 0; $index--) {
|
|
if ($buildStatements[$index].StartsWith('build ')) {
|
|
$buildIndex = $index
|
|
break
|
|
}
|
|
}
|
|
|
|
if ($buildIndex -lt 0) {
|
|
throw "Unable to locate the build statement that owns $RspRelativePath."
|
|
}
|
|
|
|
$buildLine = $buildStatements[$buildIndex]
|
|
if ($buildLine -notmatch '^build\s+\S+:\s+\S+\s+(.+)$') {
|
|
throw "Unable to parse build statement for $RspRelativePath."
|
|
}
|
|
|
|
$rspContents = ($Matches[1] -split '\s+') -join [Environment]::NewLine
|
|
$rspPath = Join-Path $BuildDir $RspRelativePath
|
|
$rspParent = Split-Path -Parent $rspPath
|
|
|
|
if (-not (Test-Path -LiteralPath $rspParent)) {
|
|
New-Item -ItemType Directory -Path $rspParent | Out-Null
|
|
}
|
|
|
|
Set-Content -LiteralPath $rspPath -Value $rspContents
|
|
}
|
|
|
|
function Ensure-NinjaRspFiles {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$CommandLine
|
|
)
|
|
|
|
$rspMatches = [regex]::Matches($CommandLine, '@(?<path>[^\s"]+\.rsp)')
|
|
foreach ($rspMatch in $rspMatches) {
|
|
$rspRelativePath = $rspMatch.Groups['path'].Value
|
|
$rspPath = Join-Path $BuildDir $rspRelativePath
|
|
if (-not (Test-Path -LiteralPath $rspPath)) {
|
|
New-NinjaRspFile -BuildDir $BuildDir -RspRelativePath $rspRelativePath
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-PendingNinjaCommands {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir
|
|
)
|
|
|
|
$commandLines = & ninja -C $BuildDir -t commands
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Unable to enumerate pending Ninja commands in $BuildDir"
|
|
}
|
|
|
|
return $commandLines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
|
}
|
|
|
|
function Invoke-NinjaFallbackBuild {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir
|
|
)
|
|
|
|
$pendingCommands = Get-PendingNinjaCommands -BuildDir $BuildDir
|
|
if ($pendingCommands.Count -eq 0) {
|
|
return
|
|
}
|
|
|
|
Push-Location $BuildDir
|
|
try {
|
|
foreach ($pendingCommand in $pendingCommands) {
|
|
Ensure-NinjaRspFiles -BuildDir $BuildDir -CommandLine $pendingCommand
|
|
|
|
$commandToRun = $pendingCommand -replace '\s/showIncludes(?=\s|$)', ''
|
|
|
|
if ($commandToRun -match '^[^ ]*cmd(?:\.exe)?\s+/C\s+"(?<inner>.*)"\s*$') {
|
|
& cmd.exe /C $Matches['inner']
|
|
}
|
|
else {
|
|
& cmd.exe /C $commandToRun
|
|
}
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Fallback Ninja command failed with exit code ${LASTEXITCODE}: $pendingCommand"
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
}
|
|
|
|
function Invoke-CMakeBuild {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildDir,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$Parallel,
|
|
|
|
[Parameter()]
|
|
[int]$TimeoutSeconds = 0
|
|
)
|
|
|
|
Remove-NinjaLock -Path $BuildDir
|
|
$isNinjaBuild = Test-IsNinjaBuildDirectory -BuildDir $BuildDir
|
|
|
|
if ($TimeoutSeconds -le 0) {
|
|
if ($isNinjaBuild) {
|
|
Invoke-NativeCommand -FilePath 'ninja' -Arguments @(
|
|
'-C', $BuildDir,
|
|
'-j', $Parallel.ToString()
|
|
)
|
|
}
|
|
else {
|
|
Invoke-NativeCommand -FilePath 'cmake' -Arguments @(
|
|
'--build', $BuildDir,
|
|
'--parallel', $Parallel.ToString()
|
|
)
|
|
}
|
|
return
|
|
}
|
|
|
|
if ($isNinjaBuild) {
|
|
$buildToolName = 'Ninja'
|
|
$buildResult = Invoke-ProcessWithTimeout -FilePath 'ninja' -Arguments @(
|
|
'-C', $BuildDir,
|
|
'-j', $Parallel.ToString()
|
|
) -TimeoutSeconds $TimeoutSeconds
|
|
}
|
|
else {
|
|
$buildToolName = 'CMake'
|
|
$buildResult = Invoke-ProcessWithTimeout -FilePath 'cmake' -Arguments @(
|
|
'--build', $BuildDir,
|
|
'--parallel', $Parallel.ToString()
|
|
) -TimeoutSeconds $TimeoutSeconds
|
|
}
|
|
|
|
if ($buildResult.Completed -and ($buildResult.ExitCode -eq 0)) {
|
|
return
|
|
}
|
|
|
|
if (-not $isNinjaBuild) {
|
|
if (-not $buildResult.Completed) {
|
|
throw "$buildToolName build timed out after $TimeoutSeconds seconds in $BuildDir"
|
|
}
|
|
|
|
throw "$buildToolName build failed with exit code $($buildResult.ExitCode) in $BuildDir"
|
|
}
|
|
|
|
if ($buildResult.Completed) {
|
|
throw "$buildToolName build failed with exit code $($buildResult.ExitCode) in $BuildDir"
|
|
}
|
|
|
|
Write-Warning "$buildToolName build timed out after $TimeoutSeconds seconds in $BuildDir. Replaying pending Ninja commands from PowerShell."
|
|
|
|
Remove-NinjaLock -Path $BuildDir
|
|
Invoke-NinjaFallbackBuild -BuildDir $BuildDir
|
|
}
|