diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 0983f581b..79a82b1d2 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -18,10 +18,10 @@ ACIOSS ACover actctx ACTCTXW -actionmap activatable ACTIVEBORDER ACTIVECAPTION +adaa ADDALIAS ADDB ADDREF @@ -199,6 +199,7 @@ byref bytearray bytebuffer cac +cacafire callee cang capslock @@ -298,6 +299,8 @@ codepage codepath codepoint codeproject +coffgroup +coffgrp coinit COLLECTIONURI colorizing @@ -487,6 +490,7 @@ dai DATABLOCK DATAVIEW DATAW +datetime DBatch dbcs DBCSCHAR @@ -676,6 +680,7 @@ dxgidwm dxinterop dxttbmp eachother +eae eaf EASTEUROPE eb @@ -698,6 +703,7 @@ elems elif elseif emacs +emplate EMPTYBOX enabledelayedexpansion endian @@ -761,6 +767,7 @@ fcb fcharset fclose fcntl +fd fdc FDD fdopen @@ -781,6 +788,7 @@ FILESYSPATH filesystem FILETIME FILETYPE +fileurl FILEW FILLATTR FILLCONSOLEOUTPUT @@ -846,6 +854,7 @@ fuzzwrapper fwdecl fwe fwlink +Fx GAUSSIAN gb gci @@ -854,6 +863,7 @@ gcy gdi gdip gdirenderer +GENPROFILE geopol GETALIAS GETALIASES @@ -973,6 +983,7 @@ hfont hglobal hh hhh +HHmm hhook hhx HIBYTE @@ -1003,6 +1014,7 @@ horiz HORZ hostable hostlib +Hostx HPA HPAINTBUFFER hpcon @@ -1129,6 +1141,7 @@ INPUTPROCESSORPROFILE inputrc Inputreadhandledata INSERTMODE +installationpath intellisense INTERACTIVITYBASE INTERCEPTCOPYPASTE @@ -1430,6 +1443,7 @@ mkdir MMBB mmcc MMCPL +MMdd mmsystem MNC MNOPQ @@ -1618,6 +1632,7 @@ numlock numpad NUMSCROLL nupkg +nuspec NVIDIA NVR Nx @@ -1760,7 +1775,11 @@ PFNCONSOLECREATEIOTHREAD PFONT PFONTENUMDATA PFS +pgd pgdn +pgorepro +pgort +PGU pguid pgup PHANDLE @@ -1994,7 +2013,6 @@ Replymessage repositorypath rescap Resequence -reserialize RESETCONTENT resheader resizable @@ -2271,6 +2289,7 @@ STX stylecop SUA subcompartment +subfolder subkey SUBLANG sublicensable @@ -2521,7 +2540,7 @@ UNORM unparseable unpause Unregister -Unregistering +unregistering untests untextured untimes @@ -2831,7 +2850,6 @@ xdy XEncoding xes Xes -XES xff XFile XFORM @@ -2879,6 +2897,7 @@ YVIRTUALSCREEN Yw YWalk yx +yy YZ Zc ZCmd @@ -2890,3 +2909,4 @@ zsh zu zxcvbnm zy +zz diff --git a/NOTICE.md b/NOTICE.md index ccff11e17..44e0f6662 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -252,10 +252,26 @@ DEALINGS IN THE SOFTWARE. ``` +# Microsoft Open Source + +This product also incorporates source code from other Microsoft open source projects, all licensed under the MIT license. + +## `GSL` + +**Source**: [https://github.com/microsoft/GSL](https://github.com/microsoft/GSL) + +## `Microsoft-UI-XAML` + +**Source**: [https://github.com/microsoft/Microsoft-UI-XAML](https://github.com/microsoft/Microsoft-UI-XAML) + ## `VirtualDesktopUtils` **Source**: [https://github.com/microsoft/PowerToys](https://github.com/microsoft/PowerToys) +## `wil` + +**Source**: [https://github.com/microsoft/wil](https://github.com/microsoft/wil) + ### License ``` diff --git a/OpenConsole.sln b/OpenConsole.sln index 2b644ae98..60c2f7682 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -395,6 +395,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_Control", "src\ca {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsTerminal.UIA.Tests", "src\cascadia\WindowsTerminal_UIATests\WindowsTerminal.UIA.Tests.csproj", "{F19DACD5-0C6E-40DC-B6E4-767A3200542C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AuditMode|Any CPU = AuditMode|Any CPU @@ -3256,6 +3258,40 @@ Global {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|x64.Build.0 = Release|x64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|x86.ActiveCfg = Release|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|x86.Build.0 = Release|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|ARM.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|ARM64.ActiveCfg = Debug|ARM64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|DotNet_x64Test.ActiveCfg = Debug|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|DotNet_x86Test.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|x64.ActiveCfg = Debug|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|x86.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|ARM.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|ARM64.Build.0 = Debug|ARM64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|DotNet_x64Test.ActiveCfg = Debug|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|x64.ActiveCfg = Debug|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|x64.Build.0 = Debug|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|x86.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|x86.Build.0 = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|Any CPU.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|ARM.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|DotNet_x64Test.ActiveCfg = Debug|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|DotNet_x86Test.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|x64.ActiveCfg = Debug|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|x86.ActiveCfg = Debug|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|Any CPU.ActiveCfg = Release|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|ARM.ActiveCfg = Release|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|ARM64.ActiveCfg = Release|ARM64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|ARM64.Build.0 = Release|ARM64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|DotNet_x64Test.ActiveCfg = Release|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|DotNet_x86Test.ActiveCfg = Release|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|x64.ActiveCfg = Release|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|x64.Build.0 = Release|x64 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|x86.ActiveCfg = Release|Win32 + {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3353,6 +3389,7 @@ Global {9921CA0A-320C-4460-8623-3A3196E7F4CB} = {59840756-302F-44DF-AA47-441A9D673202} {05D9052F-D78F-478F-968A-2DE38A6DB996} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E} + {F19DACD5-0C6E-40DC-B6E4-767A3200542C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271} diff --git a/build/Helix/AzurePipelinesHelperScripts.ps1 b/build/Helix/AzurePipelinesHelperScripts.ps1 index 5574a9b53..8934a8548 100644 --- a/build/Helix/AzurePipelinesHelperScripts.ps1 +++ b/build/Helix/AzurePipelinesHelperScripts.ps1 @@ -29,4 +29,147 @@ function GetQueryTestRunsUri $baseUri = GetAzureDevOpsBaseUri -CollectionUri $CollectionUri -TeamProject $TeamProject $queryUri = "$baseUri/_apis/test/runs?buildUri=$BuildUri$includeRunDetailsParameter&api-version=5.0" return $queryUri +} + +function Get-HelixJobTypeFromTestRun +{ + Param ($testRun) + + $testRunSingleResultUri = "$($testRun.url)/results?`$top=1&`$skip=0&api-version=5.1" + $singleTestResult = Invoke-RestMethod -Uri $testRunSingleResultUri -Method Get -Headers $azureDevOpsRestApiHeaders + $count = $singleTestResult.value.Length + if($count -eq 0) + { + # If the count is 0, then results have not yet been reported for this run. + # We only care about completed runs with results, so it is ok to just return 'UNKNOWN' for this run. + return "UNKNOWN" + } + else + { + $info = ConvertFrom-Json $singleTestResult.value.comment + $helixJobId = $info.HelixJobId + $job = Invoke-RestMethodWithRetries "https://helix.dot.net/api/2019-06-17/jobs/${helixJobId}?access_token=${HelixAccessToken}" + return $job.Type + } +} + +function Append-HelixAccessTokenToUrl +{ + Param ([string]$url, [string]$token) + if($url.Contains("?")) + { + $url = "$($url)&access_token=$($token)" + } + else + { + $url = "$($url)?access_token=$($token)" + } + return $url +} + + +# The Helix Rest api is sometimes unreliable. So we call these apis with retry logic. +# Note: The Azure DevOps apis are stable and do not need to be called with this retry logic. +$helixApiRetries = 0 +$helixApiRetriesMax = 10 + +function Download-StringWithRetries +{ + Param ([string]$fileName, [string]$url) + + $result = "" + $done = $false + + while(!($done)) + { + try + { + Write-Host "Downloading $fileName" + $result = (New-Object System.Net.WebClient).DownloadString($url) + $done = $true + } + catch + { + Write-Host "Failed to download $fileName $($PSItem.Exception)" + + $helixApiRetries = $helixApiRetries + 1 + if($helixApiRetries -lt $helixApiRetriesMax) + { + Write-Host "Sleep and retry download of $fileName" + Start-Sleep 60 + } + else + { + throw "Failed to download $fileName" + } + } + } + + return $result +} + +function Invoke-RestMethodWithRetries +{ + Param ([string]$url,$Headers) + + $result = @() + $done = $false + + while(!($done)) + { + try + { + $result = Invoke-RestMethod -Uri $url -Method Get -Headers $Headers + $done = $true + } + catch + { + Write-Host "Failed to invoke Rest method $($PSItem.Exception)" + + $helixApiRetries = $helixApiRetries + 1 + if($helixApiRetries -lt $helixApiRetriesMax) + { + Write-Host "Sleep and retry invoke" + Start-Sleep 60 + } + else + { + throw "Failed to invoke Rest method" + } + } + } + + return $result +} + +function Download-FileWithRetries +{ + Param ([string]$fileurl, [string]$destination) + + $done = $false + + while(!($done)) + { + try + { + Write-Host "Downloading $destination" + $webClient.DownloadFile($fileurl, $destination) + $done = $true + } + catch + { + Write-Host "Failed to download $destination $($PSItem.Exception)" + + $helixApiRetries = $helixApiRetries + 1 + if($helixApiRetries -lt $helixApiRetriesMax) + { + Write-Host "Sleep and retry download of $destination" + Start-Sleep 60 + } + else + { + throw "Failed to download $destination" + } + } + } } \ No newline at end of file diff --git a/build/Helix/PrepareHelixPayload.ps1 b/build/Helix/PrepareHelixPayload.ps1 index a32464e55..a68136346 100644 --- a/build/Helix/PrepareHelixPayload.ps1 +++ b/build/Helix/PrepareHelixPayload.ps1 @@ -20,10 +20,12 @@ New-Item -ItemType Directory -Force -Path $payloadDir # Copy files from nuget packages Copy-Item "$nugetPackagesDir\microsoft.windows.apps.test.1.0.181203002\lib\netcoreapp2.1\*.dll" $payloadDir Copy-Item "$nugetPackagesDir\Microsoft.Taef.10.58.210305002\build\Binaries\$Platform\*" $payloadDir -Copy-Item "$nugetPackagesDir\Microsoft.Taef.10.58.210305002\build\Binaries\$Platform\CoreClr\*" $payloadDir +Copy-Item "$nugetPackagesDir\Microsoft.Taef.10.58.210305002\build\Binaries\$Platform\NetFx4.5\*" $payloadDir New-Item -ItemType Directory -Force -Path "$payloadDir\.NETCoreApp2.1\" Copy-Item "$nugetPackagesDir\runtime.win-$Platform.microsoft.netcore.app.2.1.0\runtimes\win-$Platform\lib\netcoreapp2.1\*" "$payloadDir\.NETCoreApp2.1\" Copy-Item "$nugetPackagesDir\runtime.win-$Platform.microsoft.netcore.app.2.1.0\runtimes\win-$Platform\native\*" "$payloadDir\.NETCoreApp2.1\" +New-Item -ItemType Directory -Force -Path "$payloadDir\content\" +Copy-Item "$nugetPackagesDir\Microsoft.Internal.Windows.Terminal.TestContent.1.0.1\content\*" "$payloadDir\content\" function Copy-If-Exists { @@ -52,3 +54,13 @@ Copy-Item "build\helix\HelixTestHelpers.cs" "$payloadDir" Copy-Item "build\helix\runtests.cmd" $payloadDir Copy-Item "build\helix\InstallTestAppDependencies.ps1" "$payloadDir" Copy-Item "build\Helix\EnsureMachineState.ps1" "$payloadDir" + +# Copy the APPX package from the 'drop' artifact dir +Copy-Item "$repoDirectory\Artifacts\$ArtifactName\appx\CascadiaPackage_0.0.1.0_$Platform.msix" $payloadDir\CascadiaPackage.zip + +# Rename it to extension of ZIP because Expand-Archive is real sassy on the build machines +# and refuses to unzip it because of its file extension while on a desktop, it just +# does the job without complaining. + +# Extract the APPX package +Expand-Archive -LiteralPath $payloadDir\CascadiaPackage.zip -DestinationPath $payloadDir\appx diff --git a/build/Helix/ProcessHelixFiles.ps1 b/build/Helix/ProcessHelixFiles.ps1 index 0d828bdb8..f74187219 100644 --- a/build/Helix/ProcessHelixFiles.ps1 +++ b/build/Helix/ProcessHelixFiles.ps1 @@ -9,11 +9,6 @@ Param( $helixLinkFile = "$OutputFolder\LinksToHelixTestFiles.html" -$accessTokenParam = "" -if($HelixAccessToken) -{ - $accessTokenParam = "?access_token=$HelixAccessToken" -} function Generate-File-Links { @@ -45,66 +40,69 @@ $azureDevOpsRestApiHeaders = @{ $queryUri = GetQueryTestRunsUri -CollectionUri $CollectionUri -TeamProject $TeamProject -BuildUri $BuildUri -IncludeRunDetails Write-Host "queryUri = $queryUri" -$testRuns = Invoke-RestMethod -Uri $queryUri -Method Get -Headers $azureDevOpsRestApiHeaders +$testRuns = Invoke-RestMethodWithRetries $queryUri -Headers $azureDevOpsRestApiHeaders $webClient = New-Object System.Net.WebClient [System.Collections.Generic.List[string]]$workItems = @() foreach ($testRun in $testRuns.value) { - $testResults = Invoke-RestMethod -Uri "$($testRun.url)/results?api-version=5.0" -Method Get -Headers $azureDevOpsRestApiHeaders + Write-Host "testRunUri = $testRun.url" + $testResults = Invoke-RestMethodWithRetries "$($testRun.url)/results?api-version=5.0" -Headers $azureDevOpsRestApiHeaders $isTestRunNameShown = $false foreach ($testResult in $testResults.value) { - if ("comment" -in $testResult) + $info = ConvertFrom-Json $testResult.comment + $helixJobId = $info.HelixJobId + $helixWorkItemName = $info.HelixWorkItemName + + $workItem = "$helixJobId-$helixWorkItemName" + + Write-Host "Helix Work Item = $workItem" + + if (-not $workItems.Contains($workItem)) { - $info = ConvertFrom-Json $testResult.comment - $helixJobId = $info.HelixJobId - $helixWorkItemName = $info.HelixWorkItemName + $workItems.Add($workItem) + $filesQueryUri = "https://helix.dot.net/api/2019-06-17/jobs/$helixJobId/workitems/$helixWorkItemName/files$accessTokenParam" + $files = Invoke-RestMethodWithRetries $filesQueryUri - $workItem = "$helixJobId-$helixWorkItemName" - - if (-not $workItems.Contains($workItem)) + $screenShots = $files | where { $_.Name.EndsWith(".jpg") } + $dumps = $files | where { $_.Name.EndsWith(".dmp") } + $pgcFiles = $files | where { $_.Name.EndsWith(".pgc") } + if ($screenShots.Count + $dumps.Count + $pgcFiles.Count -gt 0) { - $workItems.Add($workItem) - $filesQueryUri = "https://helix.dot.net/api/2019-06-17/jobs/$helixJobId/workitems/$helixWorkItemName/files$accessTokenParam" - $files = Invoke-RestMethod -Uri $filesQueryUri -Method Get - - $screenShots = $files | where { $_.Name.EndsWith(".jpg") } - $dumps = $files | where { $_.Name.EndsWith(".dmp") } - $pgcFiles = $files | where { $_.Name.EndsWith(".pgc") } - if ($screenShots.Count + $dumps.Count + $pgcFiles.Count -gt 0) + if(-Not $isTestRunNameShown) { - if(-Not $isTestRunNameShown) + Out-File -FilePath $helixLinkFile -Append -InputObject "

$($testRun.name)

" + $isTestRunNameShown = $true + } + Out-File -FilePath $helixLinkFile -Append -InputObject "

$helixWorkItemName

" + Generate-File-Links $screenShots "Screenshots" + Generate-File-Links $dumps "CrashDumps" + Generate-File-Links $pgcFiles "PGC files" + $misc = $files | where { ($screenShots -NotContains $_) -And ($dumps -NotContains $_) -And ($visualTreeVerificationFiles -NotContains $_) -And ($pgcFiles -NotContains $_) } + Generate-File-Links $misc "Misc" + + foreach($pgcFile in $pgcFiles) + { + $flavorPath = $testResult.automatedTestName.Split('.')[0] + $archPath = $testResult.automatedTestName.Split('.')[1] + $fileName = $pgcFile.Name + $fullPath = "$OutputFolder\PGO\$flavorPath\$archPath" + $destination = "$fullPath\$fileName" + + Write-Host "Copying $($pgcFile.Name) to $destination" + + if (-Not (Test-Path $fullPath)) { - Out-File -FilePath $helixLinkFile -Append -InputObject "

$($testRun.name)

" - $isTestRunNameShown = $true + New-Item $fullPath -ItemType Directory } - Out-File -FilePath $helixLinkFile -Append -InputObject "

$helixWorkItemName

" - Generate-File-Links $screenShots "Screenshots" - Generate-File-Links $dumps "CrashDumps" - Generate-File-Links $pgcFiles "PGC files" - $misc = $files | where { ($screenShots -NotContains $_) -And ($dumps -NotContains $_) -And ($visualTreeVerificationFiles -NotContains $_) -And ($pgcFiles -NotContains $_) } - Generate-File-Links $misc "Misc" - foreach($pgcFile in $pgcFiles) - { - $flavorPath = $pgcFile.Name.Split('.')[0] - $archPath = $pgcFile.Name.Split('.')[1] - $fileName = $pgcFile.Name.Remove(0, $flavorPath.length + $archPath.length + 2) - $fullPath = "$OutputFolder\PGO\$flavorPath\$archPath" - $destination = "$fullPath\$fileName" + $link = $pgcFile.Link - Write-Host "Copying $($pgcFile.Name) to $destination" + Write-Host "Downloading $link to $destination" - if (-Not (Test-Path $fullPath)) - { - New-Item $fullPath -ItemType Directory - } - - $link = "$($pgcFile.Link)$accessTokenParam" - $webClient.DownloadFile($link, $destination) - } + Download-FileWithRetries $link $destination } } } diff --git a/build/Helix/RunTestsInHelix.proj b/build/Helix/RunTestsInHelix.proj index d52066937..db00314ff 100644 --- a/build/Helix/RunTestsInHelix.proj +++ b/build/Helix/RunTestsInHelix.proj @@ -13,6 +13,8 @@ - - + + + + \ No newline at end of file diff --git a/build/Helix/packages.config b/build/Helix/packages.config index 8bf9ce197..cfe7b4815 100644 --- a/build/Helix/packages.config +++ b/build/Helix/packages.config @@ -1,6 +1,7 @@ + diff --git a/build/Helix/runtests.cmd b/build/Helix/runtests.cmd index b55ee2cf3..5c543a141 100644 --- a/build/Helix/runtests.cmd +++ b/build/Helix/runtests.cmd @@ -28,7 +28,7 @@ echo %TIME% powershell -ExecutionPolicy Bypass .\InstallTestAppDependencies.ps1 echo %TIME% -set testBinaryCandidates=TerminalApp.LocalTests.dll SettingsModel.LocalTests.dll Conhost.UIA.Tests.dll +set testBinaryCandidates=TerminalApp.LocalTests.dll SettingsModel.LocalTests.dll Conhost.UIA.Tests.dll WindowsTerminal.UIA.Tests.dll set testBinaries= for %%B in (%testBinaryCandidates%) do ( if exist %%B ( @@ -46,7 +46,6 @@ move te.wtl te_original.wtl copy /y te_original.wtl %HELIX_WORKITEM_UPLOAD_ROOT% copy /y WexLogFileOutput\*.jpg %HELIX_WORKITEM_UPLOAD_ROOT% -for /f "tokens=* delims=" %%a in ('dir /b *.pgc') do ren "%%a" "%testnameprefix%.%%~na.pgc" copy /y *.pgc %HELIX_WORKITEM_UPLOAD_ROOT% set FailedTestQuery= diff --git a/build/pipelines/pgo.yml b/build/pipelines/pgo.yml new file mode 100644 index 000000000..1c93a56d8 --- /dev/null +++ b/build/pipelines/pgo.yml @@ -0,0 +1,27 @@ +trigger: none +pr: none + +variables: + - name: runCodesignValidationInjectionBG + value: false + +# 0.0.yyMM.dd## +# 0.0.1904.0900 +name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr) + +stages: + - stage: Build_x64 + displayName: Build x64 + dependsOn: [] + condition: succeeded() + jobs: + - template: ./templates/build-console-pgo.yml + parameters: + platform: x64 + - stage: Publish_PGO_Databases + displayName: Publish PGO databases + dependsOn: ['Build_x64'] + jobs: + - template: ./templates/pgo-build-and-publish-nuget-job.yml + parameters: + pgoArtifact: 'PGO' diff --git a/build/pipelines/templates/build-console-int.yml b/build/pipelines/templates/build-console-int.yml index bf8664d15..cc051df3f 100644 --- a/build/pipelines/templates/build-console-int.yml +++ b/build/pipelines/templates/build-console-int.yml @@ -9,6 +9,7 @@ jobs: variables: BuildConfiguration: ${{ parameters.configuration }} BuildPlatform: ${{ parameters.platform }} + PGOBuildMode: 'Optimize' pool: name: Package ES Lab E diff --git a/build/pipelines/templates/build-console-pgo.yml b/build/pipelines/templates/build-console-pgo.yml new file mode 100644 index 000000000..8af4ec5d8 --- /dev/null +++ b/build/pipelines/templates/build-console-pgo.yml @@ -0,0 +1,51 @@ +parameters: + configuration: 'Release' + platform: '' + additionalBuildArguments: '' + minimumExpectedTestsExecutedCount: 1 # Sanity check for minimum expected tests to be reported + rerunPassesRequiredToAvoidFailure: 0 + +jobs: +- job: Build${{ parameters.platform }}${{ parameters.configuration }} + displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }} + variables: + BuildConfiguration: ${{ parameters.configuration }} + BuildPlatform: ${{ parameters.platform }} + PGOBuildMode: 'Instrument' + pool: "windevbuildagents" + # The public pool is also an option! + # pool: { vmImage: windows-2019 } + + steps: + - template: build-console-steps.yml + parameters: + additionalBuildArguments: ${{ parameters.additionalBuildArguments }} + +- template: helix-runtests-job.yml + parameters: + name: 'RunTestsInHelix' + dependsOn: Build${{ parameters.platform }}${{ parameters.configuration }} + condition: succeeded() + testSuite: 'PgoInstrumentationSuite' + taefQuery: '@IsPgo=true' + configuration: ${{ parameters.configuration }} + platform: ${{ parameters.platform }} + rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }} + +- template: helix-processtestresults-job.yml + parameters: + name: 'ProcessTestResults' + pgoArtifact: 'PGO' + dependsOn: + - RunTestsInHelix + condition: succeededOrFailed() + rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }} + minimumExpectedTestsExecutedCount: ${{ parameters.minimumExpectedTestsExecutedCount }} + +- template: pgo-merge-pgd-job.yml + parameters: + name: 'MergePGD' + dependsOn: + - ProcessTestResults + pgoArtifact: 'PGO' + platform: ${{ parameters.platform }} diff --git a/build/pipelines/templates/build-console-steps.yml b/build/pipelines/templates/build-console-steps.yml index e5455dc8c..95df0aca8 100644 --- a/build/pipelines/templates/build-console-steps.yml +++ b/build/pipelines/templates/build-console-steps.yml @@ -32,6 +32,29 @@ steps: restoreSolution: build/packages.config restoreDirectory: '$(Build.SourcesDirectory)\packages' +# The environment variable VCToolsInstallDir isn't defined on lab machines, so we need to retrieve it ourselves. +- script: | + "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -Latest -requires Microsoft.Component.MSBuild -property InstallationPath > %TEMP%\vsinstalldir.txt + set /p _VSINSTALLDIR15=<%TEMP%\vsinstalldir.txt + del %TEMP%\vsinstalldir.txt + call "%_VSINSTALLDIR15%\Common7\Tools\VsDevCmd.bat" + echo VCToolsInstallDir = %VCToolsInstallDir% + echo ##vso[task.setvariable variable=VCToolsInstallDir]%VCToolsInstallDir% + displayName: 'Retrieve VC tools directory' + +- task: CmdLine@1 + displayName: 'Display build machine environment variables' + inputs: + filename: 'set' + +- task: powershell@2 + displayName: 'Restore PGO database' + condition: eq(variables['PGOBuildMode'], 'Optimize') + inputs: + targetType: filePath + workingDirectory: $(Build.SourcesDirectory)\tools\PGODatabase + filePath: $(Build.SourcesDirectory)\tools\PGODatabase\restore-pgodb.ps1 + - task: VSBuild@1 displayName: 'Build solution **\OpenConsole.sln' inputs: @@ -45,6 +68,9 @@ steps: - task: PowerShell@2 displayName: 'Check MSIX for common regressions' + # PGO runtime needs its own CRT and it's in the package for convenience. + # That will make this script mad so skip since we're not shipping the PGO Instrumentation one anyway. + condition: ne(variables['PGOBuildMode'], 'Instrument') inputs: targetType: inline script: | @@ -53,6 +79,7 @@ steps: - task: powershell@2 displayName: 'Source Index PDBs' + condition: ne(variables['PGOBuildMode'], 'Instrument') inputs: targetType: filePath filePath: build\scripts\Index-Pdbs.ps1 @@ -68,13 +95,25 @@ steps: If ($Arch -Eq "x86") { $Arch = "Win32" } Write-Host "##vso[task.setvariable variable=RationalizedBuildPlatform]${Arch}" +- task: PowerShell@2 + displayName: 'Validate binaries are optimized' + condition: eq(variables['pgoBuildMode'], 'Optimize') + inputs: + targetType: inline + script: | + $Binaries = 'OpenConsole.exe', 'WindowsTerminal.exe', 'TerminalApp.dll', 'TerminalConnection.dll', 'Microsoft.Terminal.Control.dll', 'Microsoft.Terminal.Remoting.dll', 'Microsoft.Terminal.Settings.Editor.dll', 'Microsoft.Terminal.Settings.Model.dll' + foreach ($BinFile in $Binaries) + { + & "$(Build.SourcesDirectory)\tools\PGODatabase\verify-pgo.ps1" "$(Build.SourcesDirectory)/bin/$(RationalizedBuildPlatform)/$(BuildConfiguration)/$BinFile" + } + - task: PowerShell@2 displayName: 'Run Unit Tests' inputs: targetType: filePath filePath: build\scripts\Run-Tests.ps1 arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}' - condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86'))) + condition: and(and(succeeded(), ne(variables['PGOBuildMode'], 'Instrument')), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86'))) - task: PowerShell@2 displayName: 'Run Feature Tests (x64 only)' @@ -82,7 +121,7 @@ steps: targetType: filePath filePath: build\scripts\Run-Tests.ps1 arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}' - condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64')) + condition: and(and(succeeded(), ne(variables['PGOBuildMode'], 'Instrument')), eq(variables['BuildPlatform'], 'x64')) - task: PowerShell@2 displayName: 'Convert Test Logs from WTL to xUnit format' @@ -90,10 +129,11 @@ steps: targetType: filePath filePath: build\Helix\ConvertWttLogToXUnit.ps1 arguments: -WttInputPath '${{ parameters.testLogPath }}' -WttSingleRerunInputPath 'unused.wtl' -WttMultipleRerunInputPath 'unused2.wtl' -XUnitOutputPath 'onBuildMachineResults.xml' -TestNamePrefix '$(BuildConfiguration).$(BuildPlatform)' - condition: or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')) + condition: and(ne(variables['PGOBuildMode'], 'Instrument'),or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86'))) - task: PublishTestResults@2 displayName: 'Upload converted test logs' + condition: ne(variables['PGOBuildMode'], 'Instrument') inputs: testResultsFormat: 'xUnit' # Options: JUnit, NUnit, VSTest, xUnit, cTest testResultsFiles: '**/onBuildMachineResults.xml' @@ -127,24 +167,47 @@ steps: TargetFolder: '$(Build.ArtifactStagingDirectory)/appx' OverWrite: true flattenFolders: true - condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) + condition: succeeded() - task: CopyFiles@2 displayName: 'Copy outputs needed for test runs to Artifacts' inputs: Contents: | - $(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.exe - $(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.dll - $(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.xml + $(Build.SourcesDirectory)/bin/$(RationalizedBuildPlatform)/$(BuildConfiguration)/*.exe + $(Build.SourcesDirectory)/bin/$(RationalizedBuildPlatform)/$(BuildConfiguration)/*.dll + $(Build.SourcesDirectory)/bin/$(RationalizedBuildPlatform)/$(BuildConfiguration)/*.xml **/Microsoft.VCLibs.*.appx - **/TestHostApp/* + **/TestHostApp/*.exe + **/TestHostApp/*.dll + **/TestHostApp/*.xml + !**/*.pdb + !**/*.ipdb + !**/*.obj + !**/*.pch TargetFolder: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/$(BuildPlatform)/test' OverWrite: true flattenFolders: true - condition: and(and(succeeded(), eq(variables['BuildPlatform'], 'x64')), ne(variables['Build.Reason'], 'PullRequest')) + condition: succeeded() - task: PublishBuildArtifacts@1 displayName: 'Publish All Build Artifacts' inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' - ArtifactName: 'drop' + ArtifactName: 'drop' + +- task: CopyFiles@2 + displayName: 'Copy PGO databases needed for PGO instrumentation run' + inputs: + Contents: | + **/*.pgd + TargetFolder: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/PGO/$(BuildPlatform)' + OverWrite: true + flattenFolders: true + condition: and(succeeded(), eq(variables['PGOBuildMode'], 'Instrument')) + +- task: PublishBuildArtifacts@1 + displayName: 'Publish All PGO Artifacts' + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/PGO' + ArtifactName: 'PGO' + condition: and(succeeded(), eq(variables['PGOBuildMode'], 'Instrument')) diff --git a/build/pipelines/templates/helix-createprojfile-steps.yml b/build/pipelines/templates/helix-createprojfile-steps.yml index 179f667ea..e0817036b 100644 --- a/build/pipelines/templates/helix-createprojfile-steps.yml +++ b/build/pipelines/templates/helix-createprojfile-steps.yml @@ -12,4 +12,4 @@ steps: inputs: targetType: filePath filePath: build\Helix\GenerateTestProjFile.ps1 - arguments: -TestFile '${{ parameters.testFilePath }}' -OutputProjFile '$(Build.ArtifactStagingDirectory)\${{ parameters.outputProjFileName }}' -JobTestSuiteName '${{ parameters.testSuite }}' -TaefPath '$(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.58.210305002\build\Binaries\x86' -TaefQuery '${{ parameters.taefQuery }}' \ No newline at end of file + arguments: -TestFile '${{ parameters.testFilePath }}' -OutputProjFile '$(Build.ArtifactStagingDirectory)\$(BuildConfiguration)\$(BuildPlatform)\${{ parameters.outputProjFileName }}' -JobTestSuiteName '${{ parameters.testSuite }}' -TaefPath '$(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.58.210305002\build\Binaries\x86' -TaefQuery '${{ parameters.taefQuery }}' \ No newline at end of file diff --git a/build/pipelines/templates/helix-runtests-job.yml b/build/pipelines/templates/helix-runtests-job.yml index 438540291..d1868f58b 100644 --- a/build/pipelines/templates/helix-runtests-job.yml +++ b/build/pipelines/templates/helix-runtests-job.yml @@ -10,19 +10,11 @@ parameters: maxParallel: 4 rerunPassesRequiredToAvoidFailure: 5 taefQuery: '' + configuration: '' + platform: '' # if 'useBuildOutputFromBuildId' is set, we will default to using a build from this pipeline: useBuildOutputFromPipeline: $(System.DefinitionId) - matrix: - # Release_x86: - # buildPlatform: 'x86' - # buildConfiguration: 'release' - # openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml' - # closedHelixTargetQueues: 'windows.10.amd64.client19h1.xaml' - Release_x64: - buildPlatform: 'x64' - buildConfiguration: 'release' - openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml' - closedHelixTargetQueues: 'windows.10.amd64.client19h1.xaml' + openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml' jobs: - job: ${{ parameters.name }} @@ -33,8 +25,10 @@ jobs: timeoutInMinutes: 120 strategy: maxParallel: ${{ parameters.maxParallel }} - matrix: ${{ parameters.matrix }} variables: + buildConfiguration: ${{ parameters.configuration }} + buildPlatform: ${{ parameters.platform }} + openHelixTargetQueues: ${{ parameters.openHelixTargetQueues }} artifactsDir: $(Build.SourcesDirectory)\Artifacts taefPath: $(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.58.210305002\build\Binaries\$(buildPlatform) helixCommonArgs: '/binaryLogger:$(Build.SourcesDirectory)/${{parameters.name}}.$(buildPlatform).$(buildConfiguration).binlog /p:HelixBuild=$(Build.BuildId).$(buildPlatform).$(buildConfiguration) /p:Platform=$(buildPlatform) /p:Configuration=$(buildConfiguration) /p:HelixType=${{parameters.helixType}} /p:TestSuite=${{parameters.testSuite}} /p:ProjFilesPath=$(Build.ArtifactStagingDirectory) /p:rerunPassesRequiredToAvoidFailure=${{parameters.rerunPassesRequiredToAvoidFailure}}' @@ -97,9 +91,17 @@ jobs: filename: 'dir' arguments: '/s $(Build.SourcesDirectory)\HelixPayload' + - task: PowerShell@2 + displayName: 'Make artifact directories' + inputs: + targetType: inline + script: | + New-Item -ItemType Directory -Force -Path "$(Build.ArtifactStagingDirectory)\$(BuildConfiguration)\" + New-Item -ItemType Directory -Force -Path "$(Build.ArtifactStagingDirectory)\$(BuildConfiguration)\$(BuildPlatform)\" + - template: helix-createprojfile-steps.yml parameters: - condition: and(succeeded(),ne('${{ parameters.testSuite }}','NugetTestSuite')) + condition: and(succeeded(),eq('${{ parameters.testSuite }}','DevTestSuite')) testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\TerminalApp.LocalTests.dll' outputProjFileName: 'RunTestsInHelix-TerminalAppLocalTests.proj' testSuite: '${{ parameters.testSuite }}' @@ -107,7 +109,7 @@ jobs: - template: helix-createprojfile-steps.yml parameters: - condition: and(succeeded(),ne('${{ parameters.testSuite }}','NugetTestSuite')) + condition: and(succeeded(),eq('${{ parameters.testSuite }}','DevTestSuite')) testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\SettingsModel.LocalTests.dll' outputProjFileName: 'RunTestsInHelix-SettingsModelLocalTests.proj' testSuite: '${{ parameters.testSuite }}' @@ -116,12 +118,20 @@ jobs: - template: helix-createprojfile-steps.yml parameters: - condition: and(succeeded(),ne('${{ parameters.testSuite }}','NugetTestSuite')) + condition: and(succeeded(),eq('${{ parameters.testSuite }}','DevTestSuite')) testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\Conhost.UIA.Tests.dll' outputProjFileName: 'RunTestsInHelix-HostTestsUIA.proj' testSuite: '${{ parameters.testSuite }}' taefQuery: ${{ parameters.taefQuery }} + - template: helix-createprojfile-steps.yml + parameters: + condition: and(succeeded(),or(eq('${{ parameters.testSuite }}','PgoInstrumentationSuite'),eq('${{ parameters.testSuite }}','DevTestSuite'))) + testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\WindowsTerminal.UIA.Tests.dll' + outputProjFileName: 'RunTestsInHelix-WindowsTerminalUIATests.proj' + testSuite: '${{ parameters.testSuite }}' + taefQuery: ${{ parameters.taefQuery }} + - task: PublishBuildArtifacts@1 displayName: 'Publish generated .proj files' inputs: diff --git a/build/pipelines/templates/pgo-build-and-publish-nuget-job.yml b/build/pipelines/templates/pgo-build-and-publish-nuget-job.yml new file mode 100644 index 000000000..85bc6c353 --- /dev/null +++ b/build/pipelines/templates/pgo-build-and-publish-nuget-job.yml @@ -0,0 +1,62 @@ +# From our friends at MUX: https://github.com/microsoft/microsoft-ui-xaml/blob/main/build/AzurePipelinesTemplates/MUX-BuildAndPublishPGONuGet-Job.yml + +parameters: + dependsOn: '' + pgoArtifact: PGO + +jobs: +- job: BuildAndPublishPGONuGet + dependsOn: ${{ parameters.dependsOn }} + pool: + vmImage: 'windows-2019' + variables: + artifactsPath: $(Build.SourcesDirectory)\Artifacts + pgoToolsPath: $(Build.SourcesDirectory)\tools\PGODatabase + nuspecPath: $(pgoToolsPath)\NuSpecs + nuspecFilename: PGO.nuspec + + steps: + - task: DownloadBuildArtifacts@0 + inputs: + artifactName: ${{ parameters.pgoArtifact }} + downloadPath: $(artifactsPath) + + - task: NuGetToolInstaller@0 + displayName: 'Use NuGet 5.2.0' + inputs: + versionSpec: 5.2.0 + + - task: CopyFiles@2 + displayName: 'Copy pgd files to NuGet build directory' + inputs: + sourceFolder: $(artifactsPath)\${{ parameters.pgoArtifact }} + contents: '**\*.pgd' + targetFolder: $(nuspecPath)\tools + + - task: powershell@2 + displayName: 'Generate NuSpec file' + inputs: + targetType: filePath + filePath: $(pgoToolsPath)\generate-nuspec.ps1 + workingDirectory: $(pgoToolsPath) + arguments: $(nuspecPath)\$(nuspecFilename).template $(nuspecPath)\$(nuspecFilename) + + - task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2 + displayName: 'NuGet pack' + inputs: + command: pack + packagesToPack: '$(nuspecPath)\$(nuspecFilename)' + basePath: '$(nuspecPath)' + packDestination: '$(Build.ArtifactStagingDirectory)' + + - task: PublishBuildArtifacts@1 + inputs: + pathToPublish: $(Build.ArtifactStagingDirectory) + artifactName: ${{ parameters.pgoArtifact }} + + - task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2 + displayName: 'NuGet push' + inputs: + command: push + publishVstsFeed: Terminal/TerminalDependencies + packagesToPush: $(Build.ArtifactStagingDirectory)/*.nupkg \ No newline at end of file diff --git a/build/pipelines/templates/pgo-merge-pgd-job.yml b/build/pipelines/templates/pgo-merge-pgd-job.yml new file mode 100644 index 000000000..970ef68a5 --- /dev/null +++ b/build/pipelines/templates/pgo-merge-pgd-job.yml @@ -0,0 +1,90 @@ +parameters: + dependsOn: '' + pgoArtifact: PGO + platform: '' + +jobs: +- job: MergePGD + dependsOn: ${{ parameters.dependsOn }} + pool: + vmImage: 'windows-2019' + variables: + artifactsPath: $(Build.SourcesDirectory)\Artifacts + pgoArtifactsPath: $(artifactsPath)\${{ parameters.pgoArtifact }} + buildPlatform: ${{ parameters.platform }} + + steps: + # The environment variable VCToolsInstallDir isn't defined on lab machines, so we need to retrieve it ourselves. + - script: | + "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -Latest -requires Microsoft.Component.MSBuild -property InstallationPath > %TEMP%\vsinstalldir.txt + set /p _VSINSTALLDIR15=<%TEMP%\vsinstalldir.txt + del %TEMP%\vsinstalldir.txt + call "%_VSINSTALLDIR15%\Common7\Tools\VsDevCmd.bat" + echo VCToolsInstallDir = %VCToolsInstallDir% + echo ##vso[task.setvariable variable=VCToolsInstallDir]%VCToolsInstallDir% + displayName: 'Retrieve VC tools directory' + + - task: DownloadBuildArtifacts@0 + inputs: + artifactName: ${{ parameters.pgoArtifact }} + downloadPath: $(artifactsPath) + + - script: | + cd $(buildPlatform) + "%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge WindowsTerminal*.pgc WindowsTerminal.pgd + displayName: 'Merge Terminal pgc files into pgd' + workingDirectory: $(pgoArtifactsPath) + + - script: | + cd $(buildPlatform) + "%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge OpenConsole*.pgc OpenConsole.pgd + displayName: 'Merge OpenConsole pgc files into pgd' + workingDirectory: $(pgoArtifactsPath) + + - script: | + cd $(buildPlatform) + "%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge Microsoft.Terminal.Control*.pgc Microsoft.Terminal.Control.pgd + displayName: 'Merge Microsoft.Terminal.Control pgc files into pgd' + workingDirectory: $(pgoArtifactsPath) + + - script: | + cd $(buildPlatform) + "%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge Microsoft.Terminal.Remoting*.pgc Microsoft.Terminal.Remoting.pgd + displayName: 'Merge Microsoft.Terminal.Remoting pgc files into pgd' + workingDirectory: $(pgoArtifactsPath) + + - script: | + cd $(buildPlatform) + "%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge Microsoft.Terminal.Settings.Editor*.pgc Microsoft.Terminal.Settings.Editor.pgd + displayName: 'Merge Microsoft.Terminal.Settings.Editor pgc files into pgd' + workingDirectory: $(pgoArtifactsPath) + + - script: | + cd $(buildPlatform) + "%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge Microsoft.Terminal.Settings.Model*.pgc Microsoft.Terminal.Settings.Model.pgd + displayName: 'Merge Microsoft.Terminal.Settings.Model pgc files into pgd' + workingDirectory: $(pgoArtifactsPath) + + - script: | + cd $(buildPlatform) + "%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge TerminalApp*.pgc TerminalApp.pgd + displayName: 'Merge TerminalApp pgc files into pgd' + workingDirectory: $(pgoArtifactsPath) + + - script: | + cd $(buildPlatform) + "%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge TerminalConnection*.pgc TerminalConnection.pgd + displayName: 'Merge TerminalConnection pgc files into pgd' + workingDirectory: $(pgoArtifactsPath) + + - task: CopyFiles@2 + displayName: 'Copy merged pgd to artifact staging' + inputs: + sourceFolder: $(pgoArtifactsPath) + contents: '**\$(buildPlatform)\*.pgd' + targetFolder: $(Build.ArtifactStagingDirectory) + + - task: PublishBuildArtifacts@1 + inputs: + pathToPublish: $(Build.ArtifactStagingDirectory) + artifactName: ${{ parameters.pgoArtifact }} \ No newline at end of file diff --git a/src/cascadia/Remoting/dll/Microsoft.Terminal.Remoting.vcxproj b/src/cascadia/Remoting/dll/Microsoft.Terminal.Remoting.vcxproj index 7e394eac6..a2eaba668 100644 --- a/src/cascadia/Remoting/dll/Microsoft.Terminal.Remoting.vcxproj +++ b/src/cascadia/Remoting/dll/Microsoft.Terminal.Remoting.vcxproj @@ -10,6 +10,7 @@ Console true + true @@ -61,8 +62,8 @@ User32.lib;WindowsApp.lib;shell32.lib;%(AdditionalDependencies) - /INCLUDE:_DllMain@12 - /INCLUDE:DllMain + /INCLUDE:_DllMain@12 %(AdditionalOptions) + /INCLUDE:DllMain %(AdditionalOptions) diff --git a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj index f585e025e..76eb5156c 100644 --- a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj @@ -10,6 +10,7 @@ Console true + true @@ -105,8 +106,8 @@ User32.lib;WindowsApp.lib;shell32.lib;WinMM.Lib;%(AdditionalDependencies) - /INCLUDE:_DllMain@12 - /INCLUDE:DllMain + /INCLUDE:_DllMain@12 %(AdditionalOptions) + /INCLUDE:DllMain %(AdditionalOptions) diff --git a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj index 625484523..8af1cf09a 100644 --- a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj +++ b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj @@ -7,6 +7,7 @@ DynamicLibrary Console true + true @@ -87,4 +88,4 @@ - \ No newline at end of file + diff --git a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj index b1fd4b365..061b74c23 100644 --- a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj @@ -10,6 +10,7 @@ Console true + true - /INCLUDE:_DllMain@12 - /INCLUDE:DllMain + /INCLUDE:_DllMain@12 %(AdditionalOptions) + /INCLUDE:DllMain %(AdditionalOptions) diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index 1408f2a29..6567bacb5 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -20,6 +20,7 @@ Console true + true true + true @@ -115,8 +116,8 @@ User32.lib;WindowsApp.lib;shell32.lib;%(AdditionalDependencies) - /INCLUDE:_DllMain@12 - /INCLUDE:DllMain + /INCLUDE:_DllMain@12 %(AdditionalOptions) + /INCLUDE:DllMain %(AdditionalOptions) diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index 806c83c0b..d0b116ff3 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -17,11 +17,12 @@ 10.0.18362.0 Windows + true - + true @@ -170,7 +171,6 @@ - diff --git a/src/cascadia/WindowsTerminal_UIATests/Common/Globals.cs b/src/cascadia/WindowsTerminal_UIATests/Common/Globals.cs new file mode 100644 index 000000000..dccd0b561 --- /dev/null +++ b/src/cascadia/WindowsTerminal_UIATests/Common/Globals.cs @@ -0,0 +1,49 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Windows Terminal UI Automation global settings +//---------------------------------------------------------------------------------------------------------------------- +namespace WindowsTerminal.UIA.Tests.Common +{ + using System; + using WEX.TestExecution.Markup; + + public static class Globals + { + public const int Timeout = 50; // in milliseconds + public const int LongTimeout = 5000; // in milliseconds + public const int AppCreateTimeout = 3000; // in milliseconds + + public static void WaitForTimeout() + { + System.Threading.Thread.Sleep(Globals.Timeout); + } + + public static void WaitForLongTimeout() + { + System.Threading.Thread.Sleep(Globals.LongTimeout); + } + + + static string[] modules = + { + "WindowsTerminal.exe", + "OpenConsole.exe", + "Microsoft.Terminal.Control.dll", + "Microsoft.Terminal.Remoting.dll", + "Microsoft.Terminal.Settings.Editor.dll", + "Microsoft.Terminal.Settings.Model.dll", + "TerminalApp.dll", + "TerminalConnection.dll" + }; + + public static void SweepAllModules(TestContext context) + { + foreach (var mod in modules) + { + PgoManager.PgoSweepIfInstrumented(context, mod); + } + } + } +} diff --git a/src/cascadia/WindowsTerminal_UIATests/Common/NativeMethods.cs b/src/cascadia/WindowsTerminal_UIATests/Common/NativeMethods.cs new file mode 100644 index 000000000..e465ae0b1 --- /dev/null +++ b/src/cascadia/WindowsTerminal_UIATests/Common/NativeMethods.cs @@ -0,0 +1,861 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Wrapper class for storing P/Invoke and COM Interop definitions. +//---------------------------------------------------------------------------------------------------------------------- + +namespace WindowsTerminal.UIA.Tests.Common.NativeMethods +{ + using System; + using System.Drawing; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Text; + + using Microsoft.Win32; + using WEX.TestExecution; + using WEX.Logging.Interop; + + // Small extension method helpers to make C# feel closer to native. + public static class NativeExtensions + { + public static int LoWord(this int val) + { + return val & 0xffff; + } + + public static int HiWord(this int val) + { + return (val >> 16) & 0xffff; + } + } + + public static class NativeMethods + { + public static void Win32BoolHelper(bool result, string actionMessage) + { + if (!result) + { + string errorMsg = string.Format("Win32 error occurred: 0x{0:X}", Marshal.GetLastWin32Error()); + Log.Comment(errorMsg); + } + + Verify.IsTrue(result, actionMessage); + } + + public static void Win32NullHelper(IntPtr result, string actionMessage) + { + if (result == IntPtr.Zero) + { + string errorMsg = string.Format("Win32 error occurred: 0x{0:X}", Marshal.GetLastWin32Error()); + Log.Comment(errorMsg); + } + + Verify.IsNotNull(result, actionMessage); + } + } + + public static class WinCon + { + [Flags()] + public enum CONSOLE_SELECTION_INFO_FLAGS : uint + { + CONSOLE_NO_SELECTION = 0x0, + CONSOLE_SELECTION_IN_PROGRESS = 0x1, + CONSOLE_SELECTION_NOT_EMPTY = 0x2, + CONSOLE_MOUSE_SELECTION = 0x4, + CONSOLE_MOUSE_DOWN = 0x8 + } + + public enum CONSOLE_STD_HANDLE : int + { + STD_INPUT_HANDLE = -10, + STD_OUTPUT_HANDLE = -11, + STD_ERROR_HANDLE = -12 + } + + public enum CONSOLE_ATTRIBUTES : ushort + { + FOREGROUND_BLUE = 0x1, + FOREGROUND_GREEN = 0x2, + FOREGROUND_RED = 0x4, + FOREGROUND_INTENSITY = 0x8, + FOREGROUND_YELLOW = FOREGROUND_RED | FOREGROUND_GREEN, + FOREGROUND_CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE, + FOREGROUND_MAGENTA = FOREGROUND_RED | FOREGROUND_BLUE, + FOREGROUND_COLORS = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, + FOREGROUND_ALL = FOREGROUND_COLORS | FOREGROUND_INTENSITY, + BACKGROUND_BLUE = 0x10, + BACKGROUND_GREEN = 0x20, + BACKGROUND_RED = 0x40, + BACKGROUND_INTENSITY = 0x80, + BACKGROUND_YELLOW = BACKGROUND_RED | BACKGROUND_GREEN, + BACKGROUND_CYAN = BACKGROUND_GREEN | BACKGROUND_BLUE, + BACKGROUND_MAGENTA = BACKGROUND_RED | BACKGROUND_BLUE, + BACKGROUND_COLORS = BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN, + BACKGROUND_ALL = BACKGROUND_COLORS | BACKGROUND_INTENSITY, + COMMON_LVB_LEADING_BYTE = 0x100, + COMMON_LVB_TRAILING_BYTE = 0x200, + COMMON_LVB_GRID_HORIZONTAL = 0x400, + COMMON_LVB_GRID_LVERTICAL = 0x800, + COMMON_LVB_GRID_RVERTICAL = 0x1000, + COMMON_LVB_REVERSE_VIDEO = 0x4000, + COMMON_LVB_UNDERSCORE = 0x8000 + } + + public enum CONSOLE_OUTPUT_MODES : uint + { + ENABLE_PROCESSED_OUTPUT = 0x1, + ENABLE_WRAP_AT_EOL_OUTPUT = 0x2, + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 + } + + //CHAR_INFO struct, which was a union in the old days + // so we want to use LayoutKind.Explicit to mimic it as closely + // as we can + [StructLayout(LayoutKind.Explicit)] + public struct CHAR_INFO + { + [FieldOffset(0)] + internal char UnicodeChar; + [FieldOffset(0)] + internal char AsciiChar; + [FieldOffset(2)] //2 bytes seems to work properly + internal CONSOLE_ATTRIBUTES Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct COORD + { + public short X; + public short Y; + + public override string ToString() + { + return string.Format("(X:{0} Y:{1})", X, Y); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SMALL_RECT + { + public short Left; + public short Top; + public short Right; + public short Bottom; + + public short Width + { + get + { + // The API returns bottom/right as the inclusive lower-right + // corner, so we need +1 for the true width + return (short)(this.Right - this.Left + 1); + } + } + + public short Height + { + get + { + // The API returns bottom/right as the inclusive lower-right + // corner, so we need +1 for the true height + return (short)(this.Bottom - this.Top + 1); + } + } + + public override string ToString() + { + return string.Format("(L:{0} T:{1} R:{2} B:{3})", Left, Top, Right, Bottom); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct CONSOLE_CURSOR_INFO + { + public uint dwSize; + public bool bVisible; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CONSOLE_FONT_INFO + { + public int nFont; + public COORD dwFontSize; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CONSOLE_SELECTION_INFO + { + public CONSOLE_SELECTION_INFO_FLAGS Flags; + public COORD SelectionAnchor; + public SMALL_RECT Selection; + + public override string ToString() + { + return string.Format("Flags:{0:X} Anchor:{1} Selection:{2}", Flags, SelectionAnchor, Selection); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct CONSOLE_SCREEN_BUFFER_INFO + { + public COORD dwSize; + public COORD dwCursorPosition; + public CONSOLE_ATTRIBUTES wAttributes; + public SMALL_RECT srWindow; + public COORD dwMaximumWindowSize; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CONSOLE_SCREEN_BUFFER_INFO_EX + { + public uint cbSize; + public COORD dwSize; + public COORD dwCursorPosition; + public CONSOLE_ATTRIBUTES wAttributes; + public SMALL_RECT srWindow; + public COORD dwMaximumWindowSize; + + public CONSOLE_ATTRIBUTES wPopupAttributes; + public bool bFullscreenSupported; + + internal COLORREF black; + internal COLORREF darkBlue; + internal COLORREF darkGreen; + internal COLORREF darkCyan; + internal COLORREF darkRed; + internal COLORREF darkMagenta; + internal COLORREF darkYellow; + internal COLORREF gray; + internal COLORREF darkGray; + internal COLORREF blue; + internal COLORREF green; + internal COLORREF cyan; + internal COLORREF red; + internal COLORREF magenta; + internal COLORREF yellow; + internal COLORREF white; + } + + [StructLayout(LayoutKind.Sequential)] + public struct COLORREF + { + internal uint ColorDWORD; + + public COLORREF(Color color) + { + ColorDWORD = (uint)color.R + (((uint)color.G) << 8) + (((uint)color.B) << 16); + } + + public COLORREF(uint r, uint g, uint b) + { + ColorDWORD = r + (g << 8) + (b << 16); + } + + public Color GetColor() + { + return Color.FromArgb((int)(0x000000FFU & ColorDWORD), + (int)(0x0000FF00U & ColorDWORD) >> 8, (int)(0x00FF0000U & ColorDWORD) >> 16); + } + + public void SetColor(Color color) + { + ColorDWORD = (uint)color.R + (((uint)color.G) << 8) + (((uint)color.B) << 16); + } + } + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GetStdHandle(CONSOLE_STD_HANDLE nStdHandle); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool AttachConsole(UInt32 dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool FreeConsole(); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool SetConsoleTitle(string ConsoleTitle); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetConsoleMode(IntPtr hConsoleOutputHandle, out CONSOLE_OUTPUT_MODES lpMode); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint GetConsoleTitle(StringBuilder lpConsoleTitle, int nSize); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GetConsoleWindow(); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetConsoleSelectionInfo(out CONSOLE_SELECTION_INFO lpConsoleSelectionInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool SetConsoleWindowInfo(IntPtr hConsoleOutput, bool bAbsolute, [In] ref SMALL_RECT lpConsoleWindow); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetConsoleCursorInfo(IntPtr hConsoleOutput, out CONSOLE_CURSOR_INFO lpConsoleCursorInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetConsoleScreenBufferInfo(IntPtr hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO_EX ConsoleScreenBufferInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool SetConsoleScreenBufferInfoEx(IntPtr ConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO_EX ConsoleScreenBufferInfoEx); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetCurrentConsoleFont(IntPtr hConsoleOutput, bool bMaximumWindow, out CONSOLE_FONT_INFO lpConsoleCurrentFont); + + [DllImport("kernel32.dll", EntryPoint = "ReadConsoleOutputW", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool ReadConsoleOutput( + IntPtr hConsoleOutput, + /* This pointer is treated as the origin of a two-dimensional array of CHAR_INFO structures + whose size is specified by the dwBufferSize parameter.*/ + [MarshalAs(UnmanagedType.LPArray), Out] CHAR_INFO[,] lpBuffer, + COORD dwBufferSize, + COORD dwBufferCoord, + ref SMALL_RECT lpReadRegion); + + [DllImport("kernel32.dll", EntryPoint = "WriteConsoleOutputCharacterW", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool WriteConsoleOutputCharacter( + IntPtr hConsoleOutput, + string lpCharacter, + UInt32 nLength, + COORD dwWriteCoord, + ref UInt32 lpNumberOfCharsWritten); + + } + + /// + /// The definitions within this file match the winconp.h file that is generated from wincon.w + /// Please see /windows/published/main/wincon.w + /// + public static class WinConP + { + private static readonly Guid PKEY_Console_FormatId = new Guid(0x0C570607, 0x0396, 0x43DE, new byte[] { 0x9D, 0x61, 0xE3, 0x21, 0xD7, 0xDF, 0x50, 0x26 }); + + public static readonly Wtypes.PROPERTYKEY PKEY_Console_ForceV2 = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 1 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_WrapText = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 2 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_FilterOnPaste = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 3 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_CtrlKeysDisabled = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 4 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_LineSelection = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 5 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_WindowTransparency = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 6 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_TrimZeros = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 7 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_CursorType = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 8 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_CursorColor = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 9 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_InterceptCopyPaste = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 10 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_DefaultForeground = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 11 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_DefaultBackground = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 12 }; + public static readonly Wtypes.PROPERTYKEY PKEY_Console_TerminalScrolling = new Wtypes.PROPERTYKEY() { fmtid = PKEY_Console_FormatId, pid = 13 }; + + public static readonly uint NT_CONSOLE_PROPS_SIG = 0xA0000002; + public static readonly uint NT_FE_CONSOLE_PROPS_SIG = 0xA0000004; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct NT_CONSOLE_PROPS + { + public Shell32.DATABLOCK_HEADER dbh; + public short wFillAttribute; + public short wPopupFillAttribute; + public WinCon.COORD dwScreenBufferSize; + public WinCon.COORD dwWindowSize; + public WinCon.COORD dwWindowOrigin; + public int nFont; + public int nInputBufferSize; + public WinCon.COORD dwFontSize; + public uint uFontFamily; + public uint uFontWeight; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string FaceName; + public uint uCursorSize; + public int bFullScreen; + public int bQuickEdit; + public int bInsertMode; + public int bAutoPosition; + public uint uHistoryBufferSize; + public uint uNumberOfHistoryBuffers; + public int bHistoryNoDup; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public int[] ColorTable; + public uint CursorType; + public WinCon.COLORREF CursorColor; + public bool InterceptCopyPaste; + public WinCon.COLORREF DefaultForeground; + public WinCon.COLORREF DefaultBackground; + public bool TerminalScrolling; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NT_FE_CONSOLE_PROPS + { + Shell32.DATABLOCK_HEADER dbh; + uint uCodePage; + } + } + + public static class User32 + { + // http://msdn.microsoft.com/en-us/library/windows/desktop/dd162897(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public Int32 left; + public Int32 top; + public Int32 right; + public Int32 bottom; + } + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public Int32 x; + public Int32 y; + } + + public const int WHEEL_DELTA = 120; + + [DllImport("user32.dll")] + public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); + + [DllImport("user32.dll")] + public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + public const int GWL_STYLE = (-16); + public const int GWL_EXSTYLE = (-20); + + [DllImport("user32.dll", SetLastError = true)] + public static extern int GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + public static extern bool AdjustWindowRectEx(ref RECT lpRect, int dwStyle, bool bMenu, int dwExStyle); + + [DllImport("user32.dll")] + public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); + + public enum WindowMessages : UInt32 + { + WM_KEYDOWN = 0x0100, + WM_KEYUP = 0x0101, + WM_CHAR = 0x0102, + WM_MOUSEWHEEL = 0x020A, + WM_MOUSEHWHEEL = 0x020E, + WM_USER = 0x0400, + CM_SET_KEY_STATE = WM_USER + 18 + } + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessage(IntPtr hWnd, WindowMessages Msg, Int32 wParam, IntPtr lParam); + + public enum SPI : uint + { + SPI_GETWHEELSCROLLLINES = 0x0068, + SPI_GETWHEELSCROLLCHARACTERS = 0x006C + } + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern bool SystemParametersInfo(SPI uiAction, uint uiParam, ref uint pvParam, uint fWinIni); + + + public enum WinEventId : uint + { + EVENT_CONSOLE_CARET = 0x4001, + EVENT_CONSOLE_UPDATE_REGION = 0x4002, + EVENT_CONSOLE_UPDATE_SIMPLE = 0x4003, + EVENT_CONSOLE_UPDATE_SCROLL = 0x4004, + EVENT_CONSOLE_LAYOUT = 0x4005, + EVENT_CONSOLE_START_APPLICATION = 0x4006, + EVENT_CONSOLE_END_APPLICATION = 0x4007 + } + + [Flags] + public enum WinEventFlags : uint + { + WINEVENT_OUTOFCONTEXT = 0x0000, // Events are ASYNC + WINEVENT_SKIPOWNTHREAD = 0x0001, // Don't call back for events on installer's thread + WINEVENT_SKIPOWNPROCESS = 0x0002, // Don't call back for events on installer's process + WINEVENT_INCONTEXT = 0x0004, // Events are SYNC, this causes your dll to be injected into every process + } + + public delegate void WinEventDelegate(IntPtr hWinEventHook, WinEventId eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetWinEventHook(WinEventId eventMin, WinEventId eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, WinEventFlags dwFlags); + + [DllImport("user32.dll")] + public static extern bool UnhookWinEvent(IntPtr hWinEventHook); + + [DllImport("user32.dll", SetLastError = true)] + public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + public struct MSG + { + public IntPtr hwnd; + public uint message; + public IntPtr wParam; + public IntPtr lParam; + public uint time; + public POINT pt; + } + + public enum PM : uint + { + PM_NOREMOVE = 0x0000, + PM_REMOVE = 0x0001, + PM_NOYIELD = 0x0002, + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, PM wRemoveMsg); + + [DllImport("user32.dll", SetLastError = true)] + public static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr DispatchMessage(ref MSG lpmsg); + } + + public static class Shell32 + { + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb773249(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + public struct DATABLOCK_HEADER + { + public int cbSize; + public int dwSignature; + } + + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb774944(v=vs.85).aspx + // http://pinvoke.net/default.aspx/Enums/SLGP_FLAGS.html + /// IShellLink.GetPath fFlags: Flags that specify the type of path information to retrieve + [Flags()] + public enum SLGP_FLAGS + { + /// Retrieves the standard short (8.3 format) file name + SLGP_SHORTPATH = 0x1, + /// Retrieves the Universal Naming Convention (UNC) path name of the file + SLGP_UNCPRIORITY = 0x2, + /// Retrieves the raw path name. A raw path is something that might not exist and may include environment variables that need to be expanded + SLGP_RAWPATH = 0x4 + } + + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb774952(v=vs.85).aspx + // http://pinvoke.net/default.aspx/Enums/SLR_FLAGS.html + /// IShellLink.Resolve fFlags + [Flags()] + public enum SLR_FLAGS + { + /// + /// Do not display a dialog box if the link cannot be resolved. When SLR_NO_UI is set, + /// the high-order word of fFlags can be set to a time-out value that specifies the + /// maximum amount of time to be spent resolving the link. The function returns if the + /// link cannot be resolved within the time-out duration. If the high-order word is set + /// to zero, the time-out duration will be set to the default value of 3,000 milliseconds + /// (3 seconds). To specify a value, set the high word of fFlags to the desired time-out + /// duration, in milliseconds. + /// + SLR_NO_UI = 0x1, + /// Obsolete and no longer used + SLR_ANY_MATCH = 0x2, + /// If the link object has changed, update its path and list of identifiers. + /// If SLR_UPDATE is set, you do not need to call IPersistFile::IsDirty to determine + /// whether or not the link object has changed. + SLR_UPDATE = 0x4, + /// Do not update the link information + SLR_NOUPDATE = 0x8, + /// Do not execute the search heuristics + SLR_NOSEARCH = 0x10, + /// Do not use distributed link tracking + SLR_NOTRACK = 0x20, + /// Disable distributed link tracking. By default, distributed link tracking tracks + /// removable media across multiple devices based on the volume name. It also uses the + /// Universal Naming Convention (UNC) path to track remote file systems whose drive letter + /// has changed. Setting SLR_NOLINKINFO disables both types of tracking. + SLR_NOLINKINFO = 0x40, + /// Call the Microsoft Windows Installer + SLR_INVOKE_MSI = 0x80 + } + + [ComImport, Guid("00021401-0000-0000-C000-000000000046")] + public class ShellLink + { + // Making new of this class will call CoCreate e.g. new ShellLink(); + // Cast to one of the interfaces below will QueryInterface. e.g. (IPersistFile)new ShellLink(); + } + + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb774950(v=vs.85).aspx + // http://pinvoke.net/default.aspx/Interfaces/IShellLinkW.html + /// The IShellLink interface allows Shell links to be created, modified, and resolved + [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")] + public interface IShellLinkW + { + /// Retrieves the path and file name of a Shell link object + void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out WinBase.WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags); + /// Retrieves the list of item identifiers for a Shell link object + void GetIDList(out IntPtr ppidl); + /// Sets the pointer to an item identifier list (PIDL) for a Shell link object. + void SetIDList(IntPtr pidl); + /// Retrieves the description string for a Shell link object + void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName); + /// Sets the description for a Shell link object. The description can be any application-defined string + void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); + /// Retrieves the name of the working directory for a Shell link object + void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); + /// Sets the name of the working directory for a Shell link object + void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); + /// Retrieves the command-line arguments associated with a Shell link object + void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); + /// Sets the command-line arguments for a Shell link object + void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); + /// Retrieves the hot key for a Shell link object + void GetHotkey(out short pwHotkey); + /// Sets a hot key for a Shell link object + void SetHotkey(short wHotkey); + /// Retrieves the show command for a Shell link object + void GetShowCmd(out int piShowCmd); + /// Sets the show command for a Shell link object. The show command sets the initial show state of the window. + void SetShowCmd(int iShowCmd); + /// Retrieves the location (path and index) of the icon for a Shell link object + void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, + int cchIconPath, out int piIcon); + /// Sets the location (path and index) of the icon for a Shell link object + void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); + /// Sets the relative path to the Shell link object + void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved); + /// Attempts to find the target of a Shell link, even if it has been moved or renamed + void Resolve(IntPtr hwnd, SLR_FLAGS fFlags); + /// Sets the path and file name of a Shell link object + void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); + } + + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb774916(v=vs.85).aspx + // http://pinvoke.net/default.aspx/Interfaces/IShellLonkDataList.html + [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("45e2b4ae-b1c3-11d0-b92f-00a0c90312e1")] + public interface IShellLinkDataList + { + void AddDataBlock(IntPtr pDataBlock); + void CopyDataBlock(uint dwSig, out IntPtr ppDataBlock); + void RemoveDataBlock(uint dwSig); + void GetFlags(out uint pdwFlags); + void SetFlags(uint dwFlags); + } + + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms688695(v=vs.85).aspx + // http://pinvoke.net/default.aspx/Interfaces/IPersist.html + [ComImport, Guid("0000010c-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IPersist + { + [PreserveSig] + void GetClassID(out Guid pClassID); + } + + + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms687223(v=vs.85).aspx + // http://www.pinvoke.net/default.aspx/Interfaces/IPersistFile.html + [ComImport, Guid("0000010b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IPersistFile : IPersist + { + new void GetClassID(out Guid pClassID); + + [PreserveSig] + int IsDirty(); + + [PreserveSig] + void Load([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName, uint dwMode); + + [PreserveSig] + void Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName, + [In, MarshalAs(UnmanagedType.Bool)] bool fRemember); + + [PreserveSig] + void SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName); + + [PreserveSig] + void GetCurFile([In, MarshalAs(UnmanagedType.LPWStr)] string ppszFileName); + } + + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb761474(v=vs.85).aspx + // http://www.pinvoke.net/default.aspx/Interfaces/IPropertyStore.html + [ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IPropertyStore + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCount([Out] out uint cProps); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAt([In] uint iProp, out Wtypes.PROPERTYKEY pkey); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetValue([In] ref Wtypes.PROPERTYKEY key, out object pv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetValue([In] ref Wtypes.PROPERTYKEY key, [In] ref object pv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Commit(); + } + } + + public static class WinBase + { + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365740(v=vs.85).aspx + // http://www.pinvoke.net/default.aspx/Structures/WIN32_FIND_DATA.html + // The CharSet must match the CharSet of the corresponding PInvoke signature + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct WIN32_FIND_DATAW + { + public uint dwFileAttributes; + public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string cFileName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } + + public enum STARTF : Int32 + { + STARTF_TITLEISLINKNAME = 0x00000800 + } + + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct STARTUPINFO + { + public Int32 cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public Int32 dwX; + public Int32 dwY; + public Int32 dwXSize; + public Int32 dwYSize; + public Int32 dwXCountChars; + public Int32 dwYCountChars; + public Int32 dwFillAttribute; + public STARTF dwFlags; + public Int16 wShowWindow; + public Int16 cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + [StructLayout(LayoutKind.Sequential)] + public struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct STARTUPINFOEX + { + public STARTUPINFO StartupInfo; + public IntPtr lpAttributeList; + } + + [Flags] + public enum CP_CreationFlags : uint + { + CREATE_SUSPENDED = 0x4, + CREATE_NEW_CONSOLE = 0x10, + } + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern bool CreateProcess(string lpApplicationName, + string lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + bool bInheritHandles, + CP_CreationFlags dwCreationFlags, + IntPtr lpEnvironment, + string lpCurrentDirectory, + [In] ref STARTUPINFO lpStartupInfo, + out PROCESS_INFORMATION lpProcessInformation); + + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + public static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, IntPtr lpName); + + [DllImport("kernel32.dll")] + public static extern bool TerminateJobObject(IntPtr hJob, uint uExitCode); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern int ResumeThread(IntPtr hThread); + + public enum JOBOBJECTINFOCLASS : uint + { + JobObjectBasicProcessIdList = 3 + } + + [StructLayout(LayoutKind.Sequential)] + public struct JOBOBJECT_BASIC_PROCESS_ID_LIST + { + public uint NumberOfAssignedProcesses; + public uint NumberOfProcessIdsInList; + public IntPtr ProcessId; + public IntPtr ProcessId2; + } + + [DllImport("kernel32.dll")] + public static extern bool QueryInformationJobObject(IntPtr hJob, + JOBOBJECTINFOCLASS JobObjectInformationClass, + IntPtr lpJobObjectInfo, + int cbJobObjectInfoLength, + IntPtr lpReturnLength); + } + + public static class Wtypes + { + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb773381(v=vs.85).aspx + // http://pinvoke.net/default.aspx/Structures/PROPERTYKEY.html + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct PROPERTYKEY + { + public Guid fmtid; + public uint pid; + } + } + + public static class ObjBase + { + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380337(v=vs.85).aspx + // http://www.pinvoke.net/default.aspx/Enums/StgmConstants.html + [Flags] + public enum STGM + { + STGM_READ = 0x0, + STGM_WRITE = 0x1, + STGM_READWRITE = 0x2, + STGM_SHARE_DENY_NONE = 0x40, + STGM_SHARE_DENY_READ = 0x30, + STGM_SHARE_DENY_WRITE = 0x20, + STGM_SHARE_EXCLUSIVE = 0x10, + STGM_PRIORITY = 0x40000, + STGM_CREATE = 0x1000, + STGM_CONVERT = 0x20000, + STGM_FAILIFTHERE = 0x0, + STGM_DIRECT = 0x0, + STGM_TRANSACTED = 0x10000, + STGM_NOSCRATCH = 0x100000, + STGM_NOSNAPSHOT = 0x200000, + STGM_SIMPLE = 0x8000000, + STGM_DIRECT_SWMR = 0x400000, + STGM_DELETEONRELEASE = 0x4000000 + } + } +} diff --git a/src/cascadia/WindowsTerminal_UIATests/Common/PgoManager.cs b/src/cascadia/WindowsTerminal_UIATests/Common/PgoManager.cs new file mode 100644 index 000000000..7c9017282 --- /dev/null +++ b/src/cascadia/WindowsTerminal_UIATests/Common/PgoManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using WEX.Logging.Interop; +using WEX.TestExecution.Markup; + +namespace WindowsTerminal.UIA.Tests.Common +{ + public static class PgoManager + { + public static void PgoSweepIfInstrumented(TestContext context, string assemblyName) + { +#if PGO_INSTRUMENT + string pgcFileName = context.TestName; + Log.Comment($"Running pgosweep on '{assemblyName}' for test: {pgcFileName}"); + try + { + var startInfo = new ProcessStartInfo() { + FileName = Path.GetFullPath(Path.Combine(context.TestDeploymentDir, "pgosweep.exe")), + Arguments = $"{assemblyName} {assemblyName}-{pgcFileName}.pgc", + UseShellExecute = false, + RedirectStandardOutput = true + }; + using (var process = Process.Start(startInfo)) + { + var output = new StringBuilder(); + while (!process.HasExited) + { + Log.Comment(process.StandardOutput.ReadToEnd()); + } + } + } + catch (Exception ex) + { + Log.Comment("Failed trying to pgosweep. " + ex.ToString()); + throw; + } +#endif + } + } +} diff --git a/src/cascadia/WindowsTerminal_UIATests/Elements/TerminalApp.cs b/src/cascadia/WindowsTerminal_UIATests/Elements/TerminalApp.cs new file mode 100644 index 000000000..f288fba62 --- /dev/null +++ b/src/cascadia/WindowsTerminal_UIATests/Elements/TerminalApp.cs @@ -0,0 +1,176 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Helper and wrapper for generating the base test application and its UI Root node. +//---------------------------------------------------------------------------------------------------------------------- +namespace WindowsTerminal.UIA.Tests.Elements +{ + using System; + using System.IO; + + using WindowsTerminal.UIA.Tests.Common; + using WindowsTerminal.UIA.Tests.Common.NativeMethods; + + using OpenQA.Selenium.Remote; + using OpenQA.Selenium.Appium; + using OpenQA.Selenium.Appium.iOS; + using OpenQA.Selenium.Interactions; + + using WEX.Logging.Interop; + using WEX.TestExecution; + using WEX.TestExecution.Markup; + + using System.Runtime.InteropServices; + using System.Security.Principal; + using OpenQA.Selenium; + + public class TerminalApp : IDisposable + { + protected const string AppDriverUrl = "http://127.0.0.1:4723"; + + private IntPtr job; + + public IOSDriver Session { get; private set; } + public Actions Actions { get; private set; } + public AppiumWebElement UIRoot { get; private set; } + + private bool isDisposed = false; + + private TestContext context; + + public string ContentPath { get; private set; } + public string GetFullTestContentPath(string filename) + { + return Path.GetFullPath(Path.Combine(ContentPath, filename)); + } + + public TerminalApp(TestContext context, string shellToLaunch = "powershell.exe") + { + this.context = context; + + // If running locally, set WTPath to where we can find a loose deployment of Windows Terminal + // On the build machines, the scripts lay it out at the appx\ subfolder of the test deployment directory + string path = Path.GetFullPath(Path.Combine(context.TestDeploymentDir, @"appx\WindowsTerminal.exe")); + if (context.Properties.Contains("WTPath")) + { + path = (string)context.Properties["WTPath"]; + } + Log.Comment($"Windows Terminal will be launched from '{path}'"); + + // Same goes for the content directory. Set WTTestContent for where the content files are + // for running tests. + // On the build machines, the scripts lay it out at the content\ subfolder. + ContentPath = @"content"; + if (context.Properties.Contains("WTTestContent")) + { + ContentPath = (string)context.Properties["WTTestContent"]; + } + Log.Comment($"Test Content will be loaded from '{Path.GetFullPath(ContentPath)}'"); + + this.CreateProcess(path, shellToLaunch); + } + + ~TerminalApp() + { + this.Dispose(false); + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + public AppiumWebElement GetRoot() + { + return this.UIRoot; + } + + protected virtual void Dispose(bool disposing) + { + if (!this.isDisposed) + { + // ensure we're exited when this is destroyed or disposed of explicitly + this.ExitProcess(); + + this.isDisposed = true; + } + } + + private void CreateProcess(string path, string shellToLaunch) + { + string WindowTitleToFind = "WindowsTerminal.UIA.Tests"; + + job = WinBase.CreateJobObject(IntPtr.Zero, IntPtr.Zero); + NativeMethods.Win32NullHelper(job, "Creating job object to hold binaries under test."); + + Log.Comment("Attempting to launch command-line application at '{0}'", path); + + string binaryToRunPath = path; + string args = $"new-tab --title \"{WindowTitleToFind}\" --suppressApplicationTitle \"{shellToLaunch}\""; + + string launchArgs = $"{binaryToRunPath} {args}"; + + WinBase.STARTUPINFO si = new WinBase.STARTUPINFO(); + si.cb = Marshal.SizeOf(si); + + WinBase.PROCESS_INFORMATION pi = new WinBase.PROCESS_INFORMATION(); + + NativeMethods.Win32BoolHelper(WinBase.CreateProcess(null, + launchArgs, + IntPtr.Zero, + IntPtr.Zero, + false, + WinBase.CP_CreationFlags.CREATE_SUSPENDED, + IntPtr.Zero, + null, + ref si, + out pi), + "Attempting to create child host window process."); + + Log.Comment($"Host window PID: {pi.dwProcessId}"); + + NativeMethods.Win32BoolHelper(WinBase.AssignProcessToJobObject(job, pi.hProcess), "Assigning new host window (suspended) to job object."); + NativeMethods.Win32BoolHelper(-1 != WinBase.ResumeThread(pi.hThread), "Resume host window process now that it is attached and its launch of the child application will be caught in the job object."); + + Globals.WaitForTimeout(); + + DesiredCapabilities appCapabilities = new DesiredCapabilities(); + appCapabilities.SetCapability("app", @"Root"); + Session = new IOSDriver(new Uri(AppDriverUrl), appCapabilities); + + Verify.IsNotNull(Session); + Actions = new Actions(Session); + Verify.IsNotNull(Session); + + Globals.WaitForLongTimeout(); + + UIRoot = Session.FindElementByName(WindowTitleToFind); + + // Set the timeout to 15 seconds after we found the initial window. + Session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(15); + } + + private bool IsRunningAsAdmin() + { + return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); + } + + private void ExitProcess() + { + Globals.SweepAllModules(this.context); + + // Release attachment to the child process console. + WinCon.FreeConsole(); + + this.UIRoot = null; + + if (this.job != IntPtr.Zero) + { + WinBase.TerminateJobObject(this.job, 0); + } + this.job = IntPtr.Zero; + } + } +} diff --git a/src/cascadia/WindowsTerminal_UIATests/Init.cs b/src/cascadia/WindowsTerminal_UIATests/Init.cs new file mode 100644 index 000000000..80be62156 --- /dev/null +++ b/src/cascadia/WindowsTerminal_UIATests/Init.cs @@ -0,0 +1,46 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +//---------------------------------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.IO; +using WEX.Logging.Interop; +using WEX.TestExecution.Markup; + +namespace Host.Tests.UIA +{ + [TestClass] + class Init + { + static Process appDriver; + + [AssemblyInitialize] + public static void SetupAll(TestContext context) + { + Log.Comment("Searching for WinAppDriver in the same directory where this test was launched from..."); + string winAppDriver = Path.Combine(context.TestDeploymentDir, "WinAppDriver.exe"); + + Log.Comment($"Attempting to launch WinAppDriver at: {winAppDriver}"); + Log.Comment($"Working directory: {Environment.CurrentDirectory}"); + + appDriver = Process.Start(winAppDriver); + } + + [AssemblyCleanup] + public static void CleanupAll() + { + try + { + appDriver.Kill(); + } + catch + { + + } + } + } +} diff --git a/src/cascadia/WindowsTerminal_UIATests/SmokeTests.cs b/src/cascadia/WindowsTerminal_UIATests/SmokeTests.cs new file mode 100644 index 000000000..b8105dafc --- /dev/null +++ b/src/cascadia/WindowsTerminal_UIATests/SmokeTests.cs @@ -0,0 +1,179 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// UI Automation tests for verifying the basic opening/closing of the Terminal. +//---------------------------------------------------------------------------------------------------------------------- + +namespace WindowsTerminal.UIA.Tests +{ + using OpenQA.Selenium; + using WEX.TestExecution.Markup; + + using WindowsTerminal.UIA.Tests.Common; + using WindowsTerminal.UIA.Tests.Elements; + + [TestClass] + public class SmokeTests + { + public TestContext TestContext { get; set; } + + [TestMethod] + [TestProperty("IsPGO", "true")] + public void StartTerminal() + { + using (TerminalApp app = new TerminalApp(TestContext)) + { + var root = app.GetRoot(); + root.SendKeys("Hello smoke test!"); + Globals.WaitForLongTimeout(); + } + } + + [TestMethod] + [TestProperty("IsPGO", "true")] + public void RunBigTextPowershell() + { + using (TerminalApp app = new TerminalApp(TestContext)) + { + var root = app.GetRoot(); + var contentPath = app.GetFullTestContentPath("big.txt"); + root.SendKeys($"cat \"{contentPath}\""); + root.SendKeys(Keys.Enter); + System.Threading.Thread.Sleep(25000); + } + } + + [TestMethod] + [TestProperty("IsPGO", "true")] + public void RunBigTextPowershellBulk () + { + using (TerminalApp app = new TerminalApp(TestContext)) + { + var root = app.GetRoot(); + var contentPath = app.GetFullTestContentPath("big.txt"); + root.SendKeys($"Get-Content -ReadCount 0 \"{contentPath}\" | Out-Default"); + root.SendKeys(Keys.Enter); + System.Threading.Thread.Sleep(25000); + } + } + + [TestMethod] + [TestProperty("IsPGO", "true")] + public void RunBigTextCmd() + { + using (TerminalApp app = new TerminalApp(TestContext, "cmd.exe")) + { + var root = app.GetRoot(); + var contentPath = app.GetFullTestContentPath("big.txt"); + root.SendKeys($"type \"{contentPath}\""); + root.SendKeys(Keys.Enter); + System.Threading.Thread.Sleep(25000); + } + } + + [TestMethod] + [TestProperty("IsPGO", "true")] + public void RunCmatrixCmd() + { + using (TerminalApp app = new TerminalApp(TestContext, "cmd.exe")) + { + var root = app.GetRoot(); + root.SendKeys("chcp 65001" + Keys.Enter); // This output needs UTF-8 + var contentPath = app.GetFullTestContentPath("cmatrix.txt"); + root.SendKeys($"type \"{contentPath}\""); + root.SendKeys(Keys.Enter); + System.Threading.Thread.Sleep(10000); + } + } + + [TestMethod] + [TestProperty("IsPGO", "true")] + public void RunCacafireCmd() + { + using (TerminalApp app = new TerminalApp(TestContext, "cmd.exe")) + { + var root = app.GetRoot(); + root.SendKeys("chcp 65001" + Keys.Enter); // This output needs UTF-8 + var contentPath = app.GetFullTestContentPath("cacafire.txt"); + root.SendKeys($"type \"{contentPath}\""); + root.SendKeys(Keys.Enter); + System.Threading.Thread.Sleep(25000); + } + } + + [TestMethod] + [TestProperty("IsPGO", "true")] + public void RunChafaCmd() + { + using (TerminalApp app = new TerminalApp(TestContext, "cmd.exe")) + { + var root = app.GetRoot(); + root.SendKeys("chcp 65001" + Keys.Enter); // This output needs UTF-8 + var contentPath = app.GetFullTestContentPath("chafa.txt"); + root.SendKeys($"type \"{contentPath}\""); + root.SendKeys(Keys.Enter); + System.Threading.Thread.Sleep(10000); + } + } + + [TestMethod] + [TestProperty("IsPGO", "true")] + public void RunMakeKillPanes() + { + using (TerminalApp app = new TerminalApp(TestContext)) + { + var root = app.GetRoot(); + + root.SendKeys(Keys.LeftAlt + Keys.LeftShift + "+"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftAlt + Keys.LeftShift + "+"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftAlt + Keys.LeftShift + "-"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftAlt + Keys.LeftShift + "-"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftControl + Keys.LeftShift + "W"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftControl + Keys.LeftShift + "W"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftControl + Keys.LeftShift + "W"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftControl + Keys.LeftShift + "W"); + Globals.WaitForTimeout(); + + Globals.WaitForLongTimeout(); + } + } + + [TestMethod] + [TestProperty("IsPGO", "true")] + public void RunMakeKillTabs() + { + using (TerminalApp app = new TerminalApp(TestContext)) + { + var root = app.GetRoot(); + + root.SendKeys(Keys.LeftAlt + Keys.LeftShift + "T"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftAlt + Keys.LeftShift + "T"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftAlt + Keys.LeftShift + "T"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftAlt + Keys.LeftShift + "T"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftControl + Keys.LeftShift + "W"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftControl + Keys.LeftShift + "W"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftControl + Keys.LeftShift + "W"); + Globals.WaitForTimeout(); + root.SendKeys(Keys.LeftControl + Keys.LeftShift + "W"); + Globals.WaitForTimeout(); + + Globals.WaitForLongTimeout(); + } + } + } +} diff --git a/src/cascadia/WindowsTerminal_UIATests/WindowsTerminal.UIA.Tests.csproj b/src/cascadia/WindowsTerminal_UIATests/WindowsTerminal.UIA.Tests.csproj new file mode 100644 index 000000000..19e9ca319 --- /dev/null +++ b/src/cascadia/WindowsTerminal_UIATests/WindowsTerminal.UIA.Tests.csproj @@ -0,0 +1,132 @@ + + + + {F19DACD5-0C6E-40DC-B6E4-767A3200542C} + Library + Properties + WindowsTerminal.UIA.Tests + WindowsTerminal.UIA.Tests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + $(SolutionDir)\bin\$(Platform)\$(Configuration) + prompt + 4 + + + + + ARM64 + + + x64 + + + x86 + + + true + full + false + DEBUG;TRACE + + + pdbonly + true + TRACE + $(DefineConstants);PGO_INSTRUMENT + + + + ..\..\..\packages\Appium.WebDriver.3.0.0.2\lib\net45\appium-dotnet-driver.dll + + + ..\..\..\packages\Castle.Core.4.1.1\lib\net45\Castle.Core.dll + + + ..\..\..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + + + + + ..\..\..\packages\Microsoft.Taef.10.58.210305002\lib\net45\TE.Managed.dll + + + ..\..\..\packages\Microsoft.Taef.10.58.210305002\lib\net45\TE.Model.Managed.dll + + + + + ..\..\..\packages\Selenium.WebDriver.3.5.0\lib\net40\WebDriver.dll + + + ..\..\..\packages\Selenium.Support.3.5.0\lib\net40\WebDriver.Support.dll + + + ..\..\..\packages\Microsoft.Taef.10.58.210305002\lib\net45\Wex.Common.Managed.dll + + + ..\..\..\packages\Microsoft.Taef.10.58.210305002\lib\net45\Wex.Logger.Interop.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + copy $(SolutionDir)\dep\WinAppDriver\* $(OutDir)\ + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/src/cascadia/WindowsTerminal_UIATests/app.config b/src/cascadia/WindowsTerminal_UIATests/app.config new file mode 100644 index 000000000..5d6775b67 --- /dev/null +++ b/src/cascadia/WindowsTerminal_UIATests/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cascadia/WindowsTerminal_UIATests/packages.config b/src/cascadia/WindowsTerminal_UIATests/packages.config new file mode 100644 index 000000000..615b60fc2 --- /dev/null +++ b/src/cascadia/WindowsTerminal_UIATests/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/common.build.post.props b/src/common.build.post.props index e3e563b90..87416a898 100644 --- a/src/common.build.post.props +++ b/src/common.build.post.props @@ -33,6 +33,10 @@ + + + + diff --git a/src/common.build.pre.props b/src/common.build.pre.props index bbb553640..2331ac4f3 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -156,6 +156,9 @@ + + + diff --git a/src/common.pgo.compile.props b/src/common.pgo.compile.props new file mode 100644 index 000000000..4256c124d --- /dev/null +++ b/src/common.pgo.compile.props @@ -0,0 +1,19 @@ + + + + + true + + + + + + /GENPROFILE %(AdditionalOptions) + UseLinkTimeCodeGeneration + $(VC_ReferencesPath_VC_ARM)\pgort.lib;%(AdditionalDependencies) + $(VC_ReferencesPath_VC_ARM64)\pgort.lib;%(AdditionalDependencies) + $(VC_ReferencesPath_VC_x86)\pgort.lib;%(AdditionalDependencies) + $(VC_ReferencesPath_VC_x64)\pgort.lib;%(AdditionalDependencies) + + + \ No newline at end of file diff --git a/src/common.pgo.runtime.props b/src/common.pgo.runtime.props new file mode 100644 index 000000000..2f0197843 --- /dev/null +++ b/src/common.pgo.runtime.props @@ -0,0 +1,30 @@ + + + + + $(Platform) + x86 + x64 + x64 + x64 + Hostx86 + Hostx64 + $(VCToolsInstallDir)bin\$(HostFolderName)\$(PlatformShortName) + $(VCToolsInstallDir)bin\$(PlatformShortName) + + + + + %(Filename)%(Extension) + PreserveNewest + + + %(Filename)%(Extension) + PreserveNewest + + + %(Filename)%(Extension) + PreserveNewest + + + \ No newline at end of file diff --git a/src/host/exe/Host.EXE.vcxproj b/src/host/exe/Host.EXE.vcxproj index c43679ab6..3967d752f 100644 --- a/src/host/exe/Host.EXE.vcxproj +++ b/src/host/exe/Host.EXE.vcxproj @@ -7,7 +7,9 @@ Host.EXE OpenConsole Application + true + @@ -90,5 +92,5 @@ - - \ No newline at end of file + + diff --git a/tools/PGODatabase/NuSpecs/PGO.nuspec.template b/tools/PGODatabase/NuSpecs/PGO.nuspec.template new file mode 100644 index 000000000..b2529649d --- /dev/null +++ b/tools/PGODatabase/NuSpecs/PGO.nuspec.template @@ -0,0 +1,18 @@ + + + + $id + $version + PGO Database + Microsoft + Microsoft + false + PGO Database + Initial release + Copyright 2021 + + + + + + \ No newline at end of file diff --git a/tools/PGODatabase/NuSpecs/build/PGO.targets b/tools/PGODatabase/NuSpecs/build/PGO.targets new file mode 100644 index 000000000..96b7f8c9a --- /dev/null +++ b/tools/PGODatabase/NuSpecs/build/PGO.targets @@ -0,0 +1,20 @@ + + + + + $(Platform) + x86 + x64 + x64 + x64 + $(MSBuildThisFileDirectory)..\tools\$(PlatformShortName)\$(TargetName).pgd + + + + + PGUpdate + $(ProfileGuidedDatabaseFileName) + /force:pgorepro %(AdditionalOptions) + + + \ No newline at end of file diff --git a/tools/PGODatabase/PGO.version.props.template b/tools/PGODatabase/PGO.version.props.template new file mode 100644 index 000000000..3af39b85c --- /dev/null +++ b/tools/PGODatabase/PGO.version.props.template @@ -0,0 +1,8 @@ + + + + + $id + $version + + \ No newline at end of file diff --git a/tools/PGODatabase/config.ps1 b/tools/PGODatabase/config.ps1 new file mode 100644 index 000000000..a4974b397 --- /dev/null +++ b/tools/PGODatabase/config.ps1 @@ -0,0 +1,7 @@ +$pgoBranch = "main" +$packageId = "Microsoft.Internal.Windows.Terminal.PGODatabase" + +# Get release version +[xml] $customProps = ( Get-Content "..\..\custom.props" ) +$releaseVersionMajor = ( [int]::Parse( $customProps.GetElementsByTagName("VersionMajor").'#text' ) ) +$releaseVersionMinor = ( [int]::Parse( $customProps.GetElementsByTagName("VersionMinor").'#text' ) ) \ No newline at end of file diff --git a/tools/PGODatabase/generate-nuspec.ps1 b/tools/PGODatabase/generate-nuspec.ps1 new file mode 100644 index 000000000..d382e5c40 --- /dev/null +++ b/tools/PGODatabase/generate-nuspec.ps1 @@ -0,0 +1,11 @@ +Param( + [Parameter(Mandatory = $true, Position = 1)] [string] $templatePath, + [Parameter(Mandatory = $true, Position = 2)] [string] $outputPath) + +. .\version.ps1 +. .\template.ps1 +. .\config.ps1 + +$version = FormatVersion ( MakeVersion $releaseVersionMajor $releaseVersionMinor ( GetDatetimeStamp $pgoBranch ) ) +Write-Host ( "PGO INSTRUMENT: generating {0} version {1}" -f $packageId, $version ) +FillOut-Template $templatePath $outputPath @{ "version" = $version; "id" = $packageId } \ No newline at end of file diff --git a/tools/PGODatabase/readme.md b/tools/PGODatabase/readme.md new file mode 100644 index 000000000..935b0a867 --- /dev/null +++ b/tools/PGODatabase/readme.md @@ -0,0 +1,49 @@ +# Profile Guided Optimization + +**NOTE: All PGO work builds on work from Microsoft/Microsoft-UI-XAML** + +## Description + +We generate PGO database NuGet package which is versioned based on product release version and branch name/time stamp of the code that was used for instrumentation and training. In CI/release builds an initialization step enumerates all available versions, filters out those for other releases and branches. Given a list of applicable versions, it will find the one that is closest (BEFORE) the time-stamp of the last commit or a fork-point from instrumented branch. That package version will be installed and version references will be updated. The PGO branch is determined by variable $pgoBranch in tools/PGODatabase/config.ps1. It will need to be updated if a forked branch should be PGO'd. + +## Scenarios + +For the purpose of illustration, let’s assume the following is a chronological list of check-ins to two branches (main and release/2.4). Some of them have had instrumentation/training run done on them and have generated PGO NuGets (version numbers in parentheses). To simplify, let’s assume that release major and minor versions are the same for all check-in as they merely act as filters for what versions are considered to be available. + + 1b27fd5f -- main -- + 7b303f74 -- main -- + 930ff585 -- main -- 2.4.2001312227-main + 63948a75 -- main -- + 0d379b51 -- main -- + f23f1fad -- main -- 2.4.2001312205-main + bcf9adaa -- main -- + 6ef44a23 -- main -- + 310bc133 -- release/2.4 -- + 80a4ab55 -- release/2.4 -- 2.4.2001312054-release_2_4 + 18b956f6 -- release/2.4 -- + 4abd4d54 -- main -- 2.4.2001312033-main + d150eae0 -- main -- 2.4.2001312028-main + +### Optimizing on PGO’d branch + +If we are building on main (which in this example is PGO’d), the version picked will be the one that has the same major and minor versions AND branch name and is the same or is right before the SHA being built. + +E.g. + + 1b27fd5f -- 2.4.2001312227-main + f23f1fad -- 2.4.2001312205-main + bcf9adaa -- 2.4.2001312033-main + +### Optimizing release branch + +A branch which will be PGO’d requires a slightly different handling. Let’s say release/2.4 forked from main on commit 4abd4d54. Initially, it will be configured to track main and 18b956f6 will be optimized with 2.4.2001312033-main. When the configuration is changed to start tracking release/2.4 (change branch name $pgoBranch in tools/PGODatabase/config.ps1 script), it will start tracking its own branch. + +E.g. + + 18b956f6 -- if tracking main -> 2.4.2001312033-main, + if tracking release/2.4 -> ERROR (no database exists) + 310bc133 -- 2.4.2001312054-release_2_4 + +### Optimizing topic branch + +Assuming topic branch will not have a training run done, it can still use database from branch it was forked from. Let’s say we have a branch which was forked from main on 4abd4d54. If we don’t change which branch it’s tracking, it will keep using 2.4.2001312033-main. Merging main on f23f1fad into topic branch, will change used database to 2.4.2001312205-main. diff --git a/tools/PGODatabase/restore-pgodb.ps1 b/tools/PGODatabase/restore-pgodb.ps1 new file mode 100644 index 000000000..5fba8417a --- /dev/null +++ b/tools/PGODatabase/restore-pgodb.ps1 @@ -0,0 +1,47 @@ +. .\version.ps1 +. .\template.ps1 +. .\config.ps1 + +$feedUri = "https://pkgs.dev.azure.com/ms/terminal/_packaging/TerminalDependencies/nuget/v2" + +$currentVersion = MakeVersion $releaseVersionMajor $releaseVersionMinor ( GetDatetimeStamp $pgoBranch ) + +Write-Host ( "PGO OPTIMIZE: requesting {0} version {1}" -f $packageId, ( FormatVersion $currentVersion ) ) + +$packageSource = Register-PackageSource -ForceBootstrap -Name TerminalDependencies -Location $feedUri -ProviderName NuGet -Trusted +$packages = ( Find-Package $packageId -Source TerminalDependencies -AllowPrereleaseVersions -AllVersions ) | Sort-Object -Property Version -Descending + +$best = $null + +foreach ( $existing in $packages ) +{ + $existingVersion = MakeVersionFromString $existing.Version + + if ( ( CompareBranches $existingVersion $currentVersion ) -eq $False -or + ( CompareReleases $existingVersion $currentVersion ) -ne 0 ) + { + # If this is different release or branch, then skip it. + continue + } + + if ( ( CompareRevisions $existingVersion $currentVersion ) -le 0 ) + { + # Version are sorted in descending order, the first one less than or equal to the current is the one we want. + # NOTE: at this point the only difference between versions will be revision (date-time stamp) + # which is formatted as a fixed-length string, so string comparison WILL sort it correctly. + $best = $existing + break + } +} + +if ( $best -eq $null ) +{ + throw "Appropriate database cannot be found" +} + +Write-Host ( "PGO OPTIMIZE: picked {0} version {1}" -f $packageId, $best.Version ) + +$best | Install-Package -Destination ..\..\packages -Force +$packageSource | Unregister-PackageSource + +FillOut-Template "PGO.version.props.template" "PGO.version.props" @{ "version" = $best.Version; "id" = $packageId } \ No newline at end of file diff --git a/tools/PGODatabase/template.ps1 b/tools/PGODatabase/template.ps1 new file mode 100644 index 000000000..217f0037a --- /dev/null +++ b/tools/PGODatabase/template.ps1 @@ -0,0 +1,16 @@ +function Replace-Many ( $string, $dictionary ) +{ + foreach ( $key in $dictionary.Keys ) + { + $field = '$' + $key.ToString() + $string = $string.Replace( $field, $dictionary[$key].ToString() ) + } + + return $string +} + +function FillOut-Template ( $inputPath, $outputPath, $dictionary ) +{ + $replaced = Replace-Many ( Get-Content $inputPath ) $dictionary + Write-Output $replaced | Set-Content $outputPath -Force | Out-Null +} \ No newline at end of file diff --git a/tools/PGODatabase/verify-pgo.ps1 b/tools/PGODatabase/verify-pgo.ps1 new file mode 100644 index 000000000..5e4f18d94 --- /dev/null +++ b/tools/PGODatabase/verify-pgo.ps1 @@ -0,0 +1,45 @@ +Param( + [Parameter(Mandatory = $true, Position = 1)] + [string] $module) + +if ( !( Test-Path $module ) ) +{ + throw "File does not exist $module" +} + +$vsPath = &(Join-Path ${env:ProgramFiles(x86)} "\Microsoft Visual Studio\Installer\vswhere.exe") -property installationpath +Import-Module (Get-ChildItem $vsPath -Recurse -File -Filter Microsoft.VisualStudio.DevShell.dll).FullName +Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation + +$output = ( & link.exe /dump /headers /coffgroup $module ) + +$regex1 = [regex] '^\s*[A-F0-9]+ coffgrp(\s+[A-F0-9]+){4} \(PGU\)$' +$regex2 = [regex] '^\s*([A-F0-9]+\s+){2}\.text\$zz$' + +$matchFlags = 0 + +foreach ( $line in $output ) +{ + if ( $line -match $regex1 ) + { + $matchFlags = $matchFlags -bor 1 + } + + if ( $line -match $regex2 ) + { + $matchFlags = $matchFlags -bor 2 + } +} + +$optimized = $matchFlags -eq 3 + +$message = "$module is $( if ( $optimized ) { "optimized" } else { "not optimized" } )" + +if ( -not $optimized ) +{ + throw $message +} +else +{ + write-host $message +} \ No newline at end of file diff --git a/tools/PGODatabase/version.ps1 b/tools/PGODatabase/version.ps1 new file mode 100644 index 000000000..c6fb8f677 --- /dev/null +++ b/tools/PGODatabase/version.ps1 @@ -0,0 +1,75 @@ +function MakeVersion ( $major, $minor, $datetimeStamp ) +{ + $revision, $branch = $datetimeStamp.Split("-") + + if ( $branch -eq $null ) + { + $branch = "" + } + + return [PSCustomObject] @{ + Major = $major + Minor = $minor + Revision = $revision + Branch = $branch + } +} + +function MakeVersionFromString ( $str ) +{ + $parts = $str.Split(".") + return MakeVersion ( [int]::Parse($parts[0]) ) ( [int]::Parse($parts[1]) ) $parts[2] +} + +function FormatVersion ( $version ) +{ + $branch = "" + + if ( $version.Branch -ne "" ) + { + $branch = "-{0}" -f $version.Branch + } + + return "{0}.{1}.{2}{3}" -f $version.Major, $version.Minor, $version.Revision, $branch +} + +function CompareReleases ( $version1, $version2 ) +{ + $cmpMajor = [Math]::Sign($version1.Major - $version2.Major) + + if ( $cmpMajor -ne 0 ) + { + return $cmpMajor + } + + return [Math]::Sign($version1.Minor - $version2.Minor) +} + +function CompareRevisions ( $version1, $version2 ) +{ + return [Math]::Sign($version1.Revision - $version2.Revision) +} + +function CompareBranches ( $version1, $version2 ) +{ + return $version1.Branch -eq $version2.Branch +} + +function GetDatetimeStamp ( $pgoBranch ) +{ + $forkSHA = $( git merge-base origin/$pgoBranch HEAD ) + + if ( $LastExitCode -ne 0 ) + { + throw "FAILED: git merge-base" + } + + $forkDate = ( Get-Date -Date $( git log -1 $forkSHA --date=iso --pretty=format:"%ad" ) ).ToUniversalTime().ToString("yyMMddHHmm") + + if ( $LastExitCode -ne 0 ) + { + throw "FAILED: Get forkDate" + } + + return $forkDate + "-" + $pgoBranch.Replace("/", "_").Replace("-", "_").Replace(".", "_") +} \ No newline at end of file