mirror of
https://github.com/PowerShell/PSReadLine
synced 2026-06-08 12:12:13 +00:00
342 lines
11 KiB
PowerShell
342 lines
11 KiB
PowerShell
|
|
$MinimalSDKVersion = '6.0.100'
|
|
$IsWindowsEnv = [System.Environment]::OSVersion.Platform -eq "Win32NT"
|
|
$RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path
|
|
$LocalDotnetDirPath = if ($IsWindowsEnv) { "$env:LocalAppData\Microsoft\dotnet" } else { "$env:HOME/.dotnet" }
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Get the path of the currently running powershell executable.
|
|
#>
|
|
function Get-PSExePath
|
|
{
|
|
if (-not $Script:PSExePath) {
|
|
$Script:PSExePath = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName
|
|
}
|
|
return $Script:PSExePath
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Find the dotnet SDK that meets the minimal version requirement.
|
|
#>
|
|
function Find-Dotnet
|
|
{
|
|
$dotnetFile = if ($IsWindowsEnv) { "dotnet.exe" } else { "dotnet" }
|
|
$dotnetExePath = Join-Path -Path $LocalDotnetDirPath -ChildPath $dotnetFile
|
|
|
|
# If dotnet is already in the PATH, check to see if that version of dotnet can find the required SDK.
|
|
# This is "typically" the globally installed dotnet.
|
|
$foundDotnetWithRightVersion = $false
|
|
$dotnetInPath = Get-Command 'dotnet' -ErrorAction Ignore
|
|
if ($dotnetInPath) {
|
|
$foundDotnetWithRightVersion = Test-DotnetSDK $dotnetInPath.Source
|
|
}
|
|
|
|
if (-not $foundDotnetWithRightVersion) {
|
|
if (Test-DotnetSDK $dotnetExePath) {
|
|
Write-Warning "Can't find the dotnet SDK version $MinimalSDKVersion or higher, prepending '$LocalDotnetDirPath' to PATH."
|
|
$env:PATH = $LocalDotnetDirPath + [IO.Path]::PathSeparator + $env:PATH
|
|
}
|
|
else {
|
|
throw "Cannot find the dotnet SDK with the version $MinimalSDKVersion or higher. Please specify '-Bootstrap' to install build dependencies."
|
|
}
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Check if the dotnet SDK meets the minimal version requirement.
|
|
#>
|
|
function Test-DotnetSDK
|
|
{
|
|
param($dotnetExePath)
|
|
|
|
if (Test-Path $dotnetExePath) {
|
|
$installedVersion = & $dotnetExePath --version
|
|
return $installedVersion -ge $MinimalSDKVersion
|
|
}
|
|
return $false
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Install the dotnet SDK if we cannot find an existing one.
|
|
#>
|
|
function Install-Dotnet
|
|
{
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$Channel = 'release',
|
|
[string]$Version = $MinimalSDKVersion
|
|
)
|
|
|
|
try {
|
|
Find-Dotnet
|
|
return # Simply return if we find dotnet SDk with the correct version
|
|
} catch { }
|
|
|
|
$logMsg = if (Get-Command 'dotnet' -ErrorAction Ignore) {
|
|
"dotnet SDK out of date. Require '$MinimalSDKVersion' but found '$dotnetSDKVersion'. Updating dotnet."
|
|
} else {
|
|
"dotent SDK is not present. Installing dotnet SDK."
|
|
}
|
|
Write-Log $logMsg -Warning
|
|
|
|
$obtainUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain"
|
|
|
|
try {
|
|
Remove-Item $LocalDotnetDirPath -Recurse -Force -ErrorAction Ignore
|
|
$installScript = if ($IsWindowsEnv) { "dotnet-install.ps1" } else { "dotnet-install.sh" }
|
|
Invoke-WebRequest -Uri $obtainUrl/$installScript -OutFile $installScript
|
|
|
|
if ($IsWindowsEnv) {
|
|
& .\$installScript -Channel $Channel -Version $Version
|
|
} else {
|
|
bash ./$installScript -c $Channel -v $Version
|
|
}
|
|
}
|
|
finally {
|
|
Remove-Item $installScript -Force -ErrorAction Ignore
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Write log message for the build.
|
|
#>
|
|
function Write-Log
|
|
{
|
|
param(
|
|
[string] $Message,
|
|
[switch] $Warning,
|
|
[switch] $Indent
|
|
)
|
|
|
|
$foregroundColor = if ($Warning) { "Yellow" } else { "Green" }
|
|
$indentPrefix = if ($Indent) { " " } else { "" }
|
|
Write-Host -ForegroundColor $foregroundColor "${indentPrefix}${Message}"
|
|
}
|
|
|
|
|
|
$KeyboardLayoutHelperCode = @'
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
|
|
public class KeyboardLayoutHelper
|
|
{
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
static extern IntPtr LoadKeyboardLayout(string pwszKLID, uint Flags);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
static extern IntPtr GetKeyboardLayout(uint idThread);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
static extern int GetKeyboardLayoutList(int nBuff, [Out] IntPtr[] lpList);
|
|
|
|
// Used when setting the layout.
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
public static extern bool PostMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
|
|
|
|
// Used for getting the layout.
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
|
|
|
|
// Used in both getting and setting the layout
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
static extern IntPtr GetForegroundWindow();
|
|
|
|
const int WM_INPUTLANGCHANGEREQUEST = 0x0050;
|
|
|
|
private static string GetLayoutNameFromHKL(IntPtr hkl)
|
|
{
|
|
var lcid = (int)((uint)hkl & 0xffff);
|
|
return (new CultureInfo(lcid)).Name;
|
|
}
|
|
|
|
public static IEnumerable<string> GetKeyboardLayouts()
|
|
{
|
|
int cnt = GetKeyboardLayoutList(0, null);
|
|
var list = new IntPtr[cnt];
|
|
GetKeyboardLayoutList(list.Length, list);
|
|
|
|
foreach (var layout in list)
|
|
{
|
|
yield return GetLayoutNameFromHKL(layout);
|
|
}
|
|
}
|
|
|
|
public static string GetCurrentKeyboardLayout()
|
|
{
|
|
uint processId;
|
|
IntPtr layout = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), out processId));
|
|
return GetLayoutNameFromHKL(layout);
|
|
}
|
|
|
|
public static IntPtr SetKeyboardLayout(string lang)
|
|
{
|
|
var layoutId = (new CultureInfo(lang)).KeyboardLayoutId;
|
|
var layout = LoadKeyboardLayout(layoutId.ToString("x8"), 0x80);
|
|
// Hacky, but tests are probably running in a console app and the layout change
|
|
// is ignored, so post the layout change to the foreground window.
|
|
PostMessage(GetForegroundWindow(), WM_INPUTLANGCHANGEREQUEST, 0, layoutId);
|
|
// Wait a bit until the layout has been changed.
|
|
do {
|
|
Thread.Sleep(100);
|
|
} while (GetCurrentKeyboardLayout() != lang);
|
|
return layout;
|
|
}
|
|
}
|
|
'@
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Start to run the xUnit tests.
|
|
#>
|
|
function Start-TestRun
|
|
{
|
|
param(
|
|
[string]
|
|
$Configuration,
|
|
|
|
[string]
|
|
$Framework
|
|
)
|
|
|
|
$testResultFolder = 'TestResults'
|
|
|
|
function RunXunitTestsInNewProcess ([string] $Layout, [string] $OperatingSystem)
|
|
{
|
|
$filter = "FullyQualifiedName~Test.{0}_{1}" -f ($Layout -replace '-','_'), $OperatingSystem
|
|
$testResultFile = "xUnitTestResults.{0}.xml" -f $Layout
|
|
$testResultFile = Join-Path $testResultFolder $testResultFile
|
|
|
|
$stdOutput, $stdError = @(New-TemporaryFile; New-TemporaryFile)
|
|
$arguments = 'test', '--no-build', '-c', $Configuration, '-f', $Framework, '--filter', $filter, '--logger', "xunit;LogFilePath=$testResultFile"
|
|
|
|
Start-Process -FilePath dotnet -Wait -RedirectStandardOutput $stdOutput -RedirectStandardError $stdError -ArgumentList $arguments
|
|
Get-Content $stdOutput, $stdError
|
|
Remove-Item $stdOutput, $stdError
|
|
}
|
|
|
|
try
|
|
{
|
|
$env:PSREADLINE_TESTRUN = 1
|
|
Push-Location "$RepoRoot/test"
|
|
|
|
$xUnitTestExecuted = $true
|
|
if ($IsWindowsEnv)
|
|
{
|
|
if ($env:APPVEYOR -or $env:TF_BUILD)
|
|
{
|
|
# AppVeyor CI builder only has en-US keyboard layout installed.
|
|
# We have to run tests from a new process because `GetCurrentKeyboardLayout` simply fails when called from
|
|
# the `pwsh` process started by AppVeyor. Our xUnit tests depends on `GetCurrentKeyboardLayout` to tell if
|
|
# a test case should run.
|
|
RunXunitTestsInNewProcess -Layout 'en-US' -OperatingSystem 'Windows'
|
|
}
|
|
else
|
|
{
|
|
if (-not ("KeyboardLayoutHelper" -as [type]))
|
|
{
|
|
Add-Type $KeyboardLayoutHelperCode
|
|
}
|
|
|
|
try
|
|
{
|
|
$xUnitTestExecuted = $false
|
|
|
|
# Remember the current keyboard layout, changes are system wide and restoring
|
|
# is the nice thing to do.
|
|
$savedLayout = [KeyboardLayoutHelper]::GetCurrentKeyboardLayout()
|
|
|
|
# We want to run tests in as many layouts as possible. We have key info
|
|
# data for layouts that might not be installed, and tests would fail
|
|
# if we don't set the system wide layout to match the key data we'll use.
|
|
$layouts = [KeyboardLayoutHelper]::GetKeyboardLayouts()
|
|
Write-Log "Available layouts: $layouts"
|
|
|
|
foreach ($layout in $layouts)
|
|
{
|
|
if (Test-Path "KeyInfo-${layout}-windows.json")
|
|
{
|
|
Write-Log "Testing $layout ..."
|
|
$null = [KeyboardLayoutHelper]::SetKeyboardLayout($layout)
|
|
|
|
# We have to use Start-Process so it creates a new window, because the keyboard
|
|
# layout change won't be picked up by any processes running in the current conhost.
|
|
RunXunitTestsInNewProcess -Layout $layout -OperatingSystem 'Windows'
|
|
$xUnitTestExecuted = $true
|
|
}
|
|
else
|
|
{
|
|
Write-Log "Testing not supported for the keyboard layout '$layout'."
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
# Restore the original keyboard layout
|
|
$null = [KeyboardLayoutHelper]::SetKeyboardLayout($savedLayout)
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RunXunitTestsInNewProcess -Layout 'en-US' -OperatingSystem 'Linux'
|
|
}
|
|
|
|
if ($xUnitTestExecuted)
|
|
{
|
|
# Check to see if there were any failures in xUnit tests, and throw exception to fail the build if so.
|
|
Get-ChildItem $testResultFolder | Test-XUnitTestResults > $null
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
Pop-Location
|
|
Remove-Item env:PSREADLINE_TESTRUN
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Check to see if the xUnit test run was successful.
|
|
#>
|
|
function Test-XUnitTestResults
|
|
{
|
|
param(
|
|
[Parameter(Mandatory, ValueFromPipeline)]
|
|
[string] $TestResultsFile
|
|
)
|
|
|
|
Process
|
|
{
|
|
if (-not (Test-Path $TestResultsFile))
|
|
{
|
|
throw "File not found $TestResultsFile"
|
|
}
|
|
|
|
try
|
|
{
|
|
$results = [xml] (Get-Content $TestResultsFile)
|
|
}
|
|
catch
|
|
{
|
|
throw "Cannot convert $TestResultsFile to xml : $($_.message)"
|
|
}
|
|
|
|
$failedTests = $results.assemblies.assembly.collection | Where-Object failed -gt 0
|
|
|
|
if (-not $failedTests)
|
|
{
|
|
return $true
|
|
}
|
|
|
|
throw "$($failedTests.failed) tests failed"
|
|
}
|
|
}
|