diff --git a/.nuget/packages.config b/.nuget/packages.config index eb5a8e4..1137507 100644 --- a/.nuget/packages.config +++ b/.nuget/packages.config @@ -1,4 +1,5 @@  + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 44fc225..0c0e57a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,28 +42,39 @@ On the command line, you can run the following commands from the solution direct ```batch nuget install xunit.runner.console -outputdirectory packages -packages\xunit.runner.console.\tools\xunit.runner.console bin\Debug\vswhere.test.dll +packages\xunit.runner.console.\tools\xunit.runner.console test\VSSetup.PowerShell.Test\bin\Debug\Microsoft.VisualStudio.Setup.PowerShell.Test.dll ``` -It's also recommended that, if your machine supports it, you install [Docker for Windows](https://www.docker.com/products/overview), switch to Windows containers, and test in isolated containers for runtime behavior. +If your machine supports it, you can install [Docker for Windows][docker], switch to Windows containers, and test in isolated containers for runtime behavior. You can run unit tests and integration tests together. ```batch -REM You only need to build once unless updating the Dockerfile or files it copies. -docker\build - -REM This will automatically map build output. Defaults to Debug configuration. Pass -? for options. -docker\test +tools\test.cmd ``` -For a faster development process, you can run `docker\run -detach`, copy the container ID printed to the window, then subsequently run `docker\test -on ` replacing `` with the container ID you copied previously. You can make changes to the test data and even rebuild the project and run this command again as frequently as you need. This is especially handy for quick turn around when debugging and fixing a problem. +For a faster development process, you can run `docker-compose run test` from the _docker_ directory to start an interactive PowerShell session in a container running the Visual Studio Remote Debugger. The build output and test scripts are mounted into the container. If you rebuild or modify the tests the container is automatically updated. You can also start the container detached and run tests faster without having to restart the container. + +```batch +cd docker +docker-compose up -d -To stop the container, run `docker stop `. If you did not pass `-keep` when you started the container it will be removed automatically. +REM Repeat following command as often as desired. +docker-compose exec test powershell.exe -c invoke-pester c:\tests -enableexit + +docker-compose stop +``` ### Debugging -You can also run `docker\run.cmd` to start an interactive shell for exploratory testing. If no other commands are passed when starting the container, the Visual Studio Remote Debugger will launch by default. Remote debugging services are discoverable on your private network. +You can run `docker-compose up -d` from the _docker_ directory to start an interactive shell for exploratory testing. If no other commands are passed when starting the container, the Visual Studio Remote Debugger will launch by default. Remote debugging services are discoverable on your private network. + +1. Click *Debug -> Attach to Process* +2. Change *Transport* to "Remote (no authentication)" +3. Click *Find* +4. Click *Select* on the container (host name will be the container name) +5. Select "powershell" under *Available Processes* +6. Click *Attach* -You can configure your project to start a remote command on a machine name that matches the short container ID returned if you ran `docker\run -detach`, or that you can discover by running `docker ps` in a separate console. +If you know the host name (by default, the container name) or IP address (depending on your network configuration for the container), you can type it into the *Qualifier* directory along with port 4022, e.g. "172.22.0.1:4022". ## Pull Requests @@ -73,3 +84,5 @@ We welcome pull requests for both bug fixes and new features that solve a common 2. All tests must pass. We have automated PR builds that will verify any PRs before they can be merged, but you are encouraged to run all tests in your development environment prior to pushing to your remote. Thank you for your contributions! + + [docker]: https://www.docker.com/products/overview diff --git a/appveyor.yml b/appveyor.yml index 2d61424..d2174e8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,8 @@ configuration: - Debug - Release +image: Visual Studio 2017 + environment: TreatWarningsAsErrors: true @@ -23,9 +25,8 @@ cache: before_build: - nuget restore -test: - assemblies: - - bin\$(configuration)\*.test.dll +test_script: + - tools\test.cmd -v artifacts: - path: bin\$(configuration) diff --git a/docker/Dockerfile b/docker/Dockerfile index 80305fd..0e6469f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,38 +1,24 @@ # Copyright (C) Microsoft Corporation. All rights reserved. # Licensed under the MIT license. See LICENSE.txt in the project root for license information. -# Require .NET Framework -FROM microsoft/windowsservercore +FROM heaths/vssetup:1.8.24 +SHELL ["powershell.exe", "-ExecutionPolicy", "Bypass", "-Command"] # Download and install Remote Debugger -SHELL ["powershell.exe", "-ExecutionPolicy", "Bypass", "-Command"] RUN $ErrorActionPreference = 'Stop' ; \ + $ProgressPreference = 'SilentlyContinue' ; \ $VerbosePreference = 'Continue' ; \ New-Item -Path C:\Downloads -Type Directory | Out-Null ; \ - Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/?LinkId=615470&clcid=0x409' -OutFile C:\Downloads\rtools_setup_x64.exe ; \ - Start-Process -Wait -FilePath C:\Downloads\rtools_setup_x64.exe -ArgumentList '-q' + Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/?LinkId=746570&clcid=0x409' -OutFile C:\Downloads\vs_remotetools.exe ; \ + Start-Process -Wait -FilePath C:\Downloads\vs_remotetools.exe -ArgumentList '-q' ; \ + Remove-Item -Path C:\Downloads\vs_remotetools.exe # Configure Remote Debugger -EXPOSE 3702 4020 4021 -RUN $ErrorActionPreference = 'Stop' ; \ - $VerbosePreference = 'Continue' ; \ - Start-Process -Wait -FilePath 'C:\Program Files\Microsoft Visual Studio 14.0\Common7\IDE\Remote Debugger\x64\msvsmon.exe' -ArgumentList '/prepcomputer', '/private', '/quiet' - -# Download and register current query APIs -ENV API_VERSION="1.8.24" -RUN $ErrorActionPreference = 'Stop' ; \ - $VerbosePreference = 'Continue' ; \ - Invoke-WebRequest -Uri "https://www.nuget.org/api/v2/package/Microsoft.VisualStudio.Setup.Configuration.Native/${env:API_VERSION}" -OutFile C:\Downloads\Microsoft.VisualStudio.Setup.Configuration.Native.zip ; \ - Expand-Archive -Path C:\Downloads\Microsoft.VisualStudio.Setup.Configuration.Native.zip -DestinationPath C:\Downloads\Microsoft.VisualStudio.Setup.Configuration.Native ; \ - C:\Windows\System32\regsvr32.exe /s C:\Downloads\Microsoft.VisualStudio.Setup.Configuration.Native\tools\x64\Microsoft.VisualStudio.Setup.Configuration.Native.dll ; \ - C:\Windows\SysWOW64\regsvr32.exe /s C:\Downloads\Microsoft.VisualStudio.Setup.Configuration.Native\tools\x86\Microsoft.VisualStudio.Setup.Configuration.Native.dll - -# Install latest version of Pester for integration testing +EXPOSE 3702 4022 4023 RUN $ErrorActionPreference = 'Stop' ; \ $VerbosePreference = 'Continue' ; \ - Install-PackageProvider -Name nuget -MinimumVersion 2.8.5.201 -Force ; \ - Install-Module -Name Pester -Scope CurrentUser -SkipPublisherCheck -Force + Start-Process -Wait -FilePath 'C:\Program Files\Microsoft Visual Studio 15.0\Common7\IDE\Remote Debugger\x64\msvsmon.exe' -ArgumentList '/prepcomputer', '/private', '/quiet' # Start Remote Debugger if no other command is passed to PowerShell ENTRYPOINT ["powershell.exe", "-ExecutionPolicy", "Unrestricted"] -CMD ["-NoExit", "-Command", "& 'C:\\Program Files\\Microsoft Visual Studio 14.0\\Common7\\IDE\\Remote Debugger\\x64\\msvsmon.exe' /silent /noauth /anyuser"] +CMD ["-NoExit", "-Command", "& 'C:\\Program Files\\Microsoft Visual Studio 15.0\\Common7\\IDE\\Remote Debugger\\x64\\msvsmon.exe' /silent /noauth /anyuser"] diff --git a/docker/Tests/.gitignore b/docker/Tests/.gitignore new file mode 100644 index 0000000..b2d939f --- /dev/null +++ b/docker/Tests/.gitignore @@ -0,0 +1 @@ +[Rr]esults.xml diff --git a/docker/Tests/legacy.tests.ps1 b/docker/Tests/legacy.tests.ps1 index 1a0de2f..dea71c9 100644 --- a/docker/Tests/legacy.tests.ps1 +++ b/docker/Tests/legacy.tests.ps1 @@ -54,24 +54,17 @@ Describe 'vswhere -legacy' { Context 'no instances' { BeforeEach { - New-Item HKLM:\Software\WOW6432Node\Microsoft\VisualStudio\SxS\VS7 -Force | ForEach-Object { - foreach ($version in '10.0', '14.0') { - $_ | New-ItemProperty -Name $version -Value "C:\VisualStudio\$version" -Force - } - } + New-Item HKLM:\Software\WOW6432Node\Microsoft\VisualStudio\SxS\VS7 -Force | ForEach-Object { + foreach ($version in '10.0', '14.0') { + $_ | New-ItemProperty -Name $version -Value "C:\VisualStudio\$version" -Force + } + } - Start-Process -Wait -FilePath C:\Windows\SysWOW64\regsvr32.exe -ArgumentList @( - '/s', - '/u', - 'C:\Downloads\Microsoft.VisualStudio.Setup.Configuration.Native\tools\x86\Microsoft.VisualStudio.Setup.Configuration.Native.dll' - ) + Rename-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node\Classes\CLSID\{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}\InprocServer32' -Name '(Default)' -NewName '_Default' } AfterEach { - Start-Process -Wait -FilePath C:\Windows\SysWOW64\regsvr32.exe -ArgumentList @( - '/s', - 'C:\Downloads\Microsoft.VisualStudio.Setup.Configuration.Native\tools\x86\Microsoft.VisualStudio.Setup.Configuration.Native.dll' - ) + Rename-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node\Classes\CLSID\{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}\InprocServer32' -Name '_Default' -NewName '(Default)' } It 'returns 2 instances' { diff --git a/docker/Tests/vswhere.tests.ps1 b/docker/Tests/vswhere.tests.ps1 index 0980a2c..1f8a885 100644 --- a/docker/Tests/vswhere.tests.ps1 +++ b/docker/Tests/vswhere.tests.ps1 @@ -12,18 +12,11 @@ Describe 'vswhere' { Context '(query provider not registered)' { BeforeAll { - Start-Process -Wait -FilePath C:\Windows\SysWOW64\regsvr32.exe -ArgumentList @( - '/s', - '/u', - 'C:\Downloads\Microsoft.VisualStudio.Setup.Configuration.Native\tools\x86\Microsoft.VisualStudio.Setup.Configuration.Native.dll' - ) + Rename-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node\Classes\CLSID\{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}\InprocServer32' -Name '(Default)' -NewName '_Default' } AfterAll { - Start-Process -Wait -FilePath C:\Windows\SysWOW64\regsvr32.exe -ArgumentList @( - '/s', - 'C:\Downloads\Microsoft.VisualStudio.Setup.Configuration.Native\tools\x86\Microsoft.VisualStudio.Setup.Configuration.Native.dll' - ) + Rename-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node\Classes\CLSID\{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}\InprocServer32' -Name '_Default' -NewName '(Default)' } It 'header contains no query version' { diff --git a/docker/appveyor/Dockerfile b/docker/appveyor/Dockerfile new file mode 100644 index 0000000..c8986ae --- /dev/null +++ b/docker/appveyor/Dockerfile @@ -0,0 +1,6 @@ +# Copyright (C) Microsoft Corporation. All rights reserved. +# Licensed under the MIT license. See LICENSE.txt in the project root for license information. + +# Would prefer to only use "image" in docker-compose.yml, but AppVeyor version of docker-compose fails +# stating that a "Dockerfile" in the current directory does not exist. +FROM heaths/vssetup:1.8.24 diff --git a/docker/appveyor/docker-compose.yml b/docker/appveyor/docker-compose.yml new file mode 100644 index 0000000..8fc7b17 --- /dev/null +++ b/docker/appveyor/docker-compose.yml @@ -0,0 +1,12 @@ +# Copyright (C) Microsoft Corporation. All rights reserved. +# Licensed under the MIT license. See LICENSE.txt in the project root for license information. + +version: "2.1" +services: + test: + # Need to override build context since AppVeyor version of docker-compose fails with just "image". + build: . + extends: + file: ../docker-compose.yml + service: test + command: -c Invoke-Pester C:\Tests -EnableExit -OutputFile C:\Tests\Results.xml -OutputFormat NUnitXml diff --git a/docker/build.cmd b/docker/build.cmd deleted file mode 100644 index eb1521c..0000000 --- a/docker/build.cmd +++ /dev/null @@ -1,64 +0,0 @@ -@echo off - -REM Copyright (C) Microsoft Corporation. All rights reserved. -REM Licensed under the MIT license. See LICENSE.txt in the project root for license information. - -setlocal enabledelayedexpansion - -set projectDir=%~dp0 -set solutionDir=%projectDir:~0,-12% - -set name=vswhere/test - -:parse -if "%1"=="" goto :parse_end -if /i "%1"=="-name" set name=%2& shift& shift& goto :parse -if /i "%1"=="/name" set name=%2& shift& shift& goto :parse -if /i "%1"=="-network" set network=%2& shift& shift& goto :parse -if /i "%1"=="/network" set network=%2& shift& shift& goto :parse -if "%1"=="-?" goto :help -if "%1"=="/?" goto :help -if /i "%1"=="-help" goto :help -if /i "%1"=="/help" goto :help - -echo. -echo Unknown argument: %1 -goto :help - -:parse_end -if "%network%"=="" ( - for /f "usebackq tokens=*" %%i in (`docker network ls --filter driver^=transparent --format "{{.Name}}"`) do ( - echo Discovered transparent network: %%i - set network=%%i - ) - - echo Using network: !network! -) - -if "%network%"=="" ( - echo Error: No network selected. Cannot retrieve online resources. - exit /b 87 -) - -REM Remove trailing backslash or command may fail. -set root=%projectDir:~0,-1% - -@echo on -docker build --network "%network%" --tag %name% "%root%" -@if errorlevel 1 exit /b %ERRORLEVEL% - -@echo off -echo. -goto :EOF - -:help -echo. -echo %~nx0 [options] [-?] -echo. -echo Options: -echo -name Image name. Defaults to vswhere/test. -echo -network External network name. Defaults to discovered transparent network. -echo -? Displays this help message. -echo. - -exit /b 87 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..3af5575 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,19 @@ +# Copyright (C) Microsoft Corporation. All rights reserved. +# Licensed under the MIT license. See LICENSE.txt in the project root for license information. + +version: "2.1" +services: + test: + build: . + volumes: + - ../bin/${CONFIGURATION:-Debug}:C:/bin:ro + - ./Instances:C:/ProgramData/Microsoft/VisualStudio/Packages/_Instances:ro + - ./Tests:C:/Tests + - C:/VS/Community + - C:/VS/Professional + - C:/VS/Enterprise + - C:/BuildTools + network_mode: nat + expose: + - "3702/udp" + - "4022-4023" diff --git a/docker/run.cmd b/docker/run.cmd deleted file mode 100644 index a8bcef5..0000000 --- a/docker/run.cmd +++ /dev/null @@ -1,92 +0,0 @@ -@echo off - -REM Copyright (C) Microsoft Corporation. All rights reserved. -REM Licensed under the MIT license. See LICENSE.txt in the project root for license information. - -setlocal - -if "%script%"=="" set script=%~nx0 - -set projectDir=%~dp0 -set solutionDir=%projectDir:~0,-7% - -set configuration=Debug -set name=vswhere/test -set mode=-it - -:parse -if "%1"=="" goto :parse_end -if not "%args%"=="" set args=%args% %1& shift& goto :parse -if /i "%1"=="-name" set name=%2& shift& shift& goto :parse -if /i "%1"=="/name" set name=%2& shift& shift& goto :parse -if /i "%1"=="-configuration" set configuration=%2& shift& shift& goto :parse -if /i "%1"=="/configuration" set configuration=%2& shift& shift& goto :parse -if /i "%1"=="-detach" set mode=-d& shift& goto :parse -if /i "%1"=="/detach" set mode=-d& shift& goto :parse -if /i "%1"=="-on" set id=%2& shift& shift& goto :parse -if /i "%1"=="/on" set id=%2& shift& shift& goto :parse -if /i "%1"=="-network" set params=%params% --network "%2"& shift& shift& goto :parse -if /i "%1"=="/network" set params=%params% --network "%2"& shift& shift& goto :parse -if /i "%1"=="-keep" set keep=1& shift& goto :parse -if /i "%1"=="/keep" set keep=1& shift& goto :parse -if "%1"=="-?" goto :help -if "%1"=="/?" goto :help -if /i "%1"=="-help" goto :help -if /i "%1"=="/help" goto :help -if "%1"=="--" set args=%2& shift& shift& goto :parse - -echo. -echo Unknown argument: %1 -goto :help - -:parse_end -if "%keep%"=="" set params=%params% --rm - -set outputPath=%solutionDir%bin\%configuration% -set volumes=-v %outputPath%:C:\bin -set volumes=%volumes% -v "%projectDir%Instances:C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances:ro" -set volumes=%volumes% -v C:\VS\Community -set volumes=%volumes% -v C:\VS\Professional -set volumes=%volumes% -v C:\VS\Enterprise -set volumes=%volumes% -v C:\BuildTools -set volumes=%volumes% -v "%projectDir%Tests:C:\Tests" - -if "%id%"=="" ( - REM Uses the ENTRYPOINT declaration in the Dockerfile - set cmd=docker run %mode% %volumes%%params% %name% %args% -) else ( - REM Keep in sync with the ENTRYPOINT in the Dockerfile - set cmd=docker exec %mode% %id% powershell.exe -ExecutionPolicy Unrestricted %args% -) - -echo %cmd% -call %cmd% -@if errorlevel 1 exit /b %ERRORLEVEL% - -echo. -goto :EOF - -:help -set usage=%script% [options] [-?] -if "%noargs%"=="" ( - set usage=%usage% [-- args] -) -echo. -echo %usage% -echo. -echo Options: -echo -name value Image name. Defaults to vswhere/test. -echo -configuration value The build configuration to map. Defaults to Debug. -echo -detach Detach from the container and show the ID. -echo -on value Run command on specified container ID. -echo -network value External network name. Defaults to discovered transparent network. -echo -keep Do not delete the container after exiting. -echo -? Displays this help message. -echo. -if "%noargs%"=="" ( -echo Arguments: -echo -- Any arguments after -- are passed to the container entry point. -echo. -) - -exit /b 87 diff --git a/docker/test.cmd b/tools/test.cmd similarity index 58% rename from docker/test.cmd rename to tools/test.cmd index 5561dfa..de56359 100644 --- a/docker/test.cmd +++ b/tools/test.cmd @@ -1,11 +1,6 @@ -@echo off +@if not defined _echo echo off REM Copyright (C) Microsoft Corporation. All rights reserved. REM Licensed under the MIT license. See LICENSE.txt in the project root for license information. -setlocal - -set script=%~nx0 -set noargs=1 - -call %~dp0run.cmd %* -- -c Invoke-Pester C:\Tests -EnableExit +powershell.exe -NoLogo -ExecutionPolicy Bypass -Command "%~dp0\test.ps1" %* diff --git a/tools/test.ps1 b/tools/test.ps1 new file mode 100644 index 0000000..d5e58ad --- /dev/null +++ b/tools/test.ps1 @@ -0,0 +1,109 @@ +# Copyright (C) Microsoft Corporation. All rights reserved. +# Licensed under the MIT license. See LICENSE.txt in the project root for license information. + +[CmdletBinding()] +param ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] $Configuration = $env:CONFIGURATION, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string] $Platform = $env:PLATFORM, + + [Parameter()] + [ValidateSet('Unit', 'Integration')] + [string[]] $Type = @('Unit', 'Integration') +) + +if (-not $Configuration) { + $Configuration = 'Debug' +} + +if (-not $Platform) { + $Platform = 'x86' +} + +[bool] $Failed = $false + +if ($Type -contains 'Unit') +{ + # Find vstest.console.exe. + $cmd = get-command vstest.console.exe -ea SilentlyContinue | select-object -expand Path + if (-not $cmd) { + $vswhere = get-childitem "$PSScriptRoot\..\packages\vswhere*" -filter vswhere.exe -recurse | select-object -first 1 -expand FullName + if (-not $vswhere) { + write-error 'Please run "nuget restore" on the solution to download vswhere.' + exit 1 + } + + $path = & $vswhere -latest -requires Microsoft.VisualStudio.Component.ManagedDesktop.Core -property installationPath + if (-not $path) { + write-error 'No instance of Visual Studio found with vstest.console.exe. Please start a developer command prompt.' + exit 1 + } + + $cmd = join-path $path 'Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe' + } + + if (-not (test-path $cmd)) { + write-error 'Could not find vstest.console.exe. Please start a developer command prompt.' + exit 1 + } + + # Set up logger for AppVeyor. + $logger = if ($env:APPVEYOR -eq 'true') { + write-verbose 'Using AppVeyor logger when running in an AppVeyor build.' + '/logger:appveyor' + } + + # Discover test containers for the current configuration. + $containers = get-childitem "bin\$Configuration" -include *.test.dll -recurse | foreach-object { + [string] $path = $_.FullName + + write-verbose "Discovered test assembly '$path'." + "$path" + } + + # Run unit tests. + & $cmd $logger $containers /parallel /platform:$Platform + if (-not $?) { + $Failed = $true + } +} + +if ($Type -contains 'Integration') +{ + # Run docker integration tests. + if (get-command docker-compose -ea SilentlyContinue) { + [string] $path = if ($env:APPVEYOR -eq 'true') { + $no_tty = '-T' + resolve-path "$PSScriptRoot\..\docker\appveyor\docker-compose.yml" + } else { + resolve-path "$PSScriptRoot\..\docker\docker-compose.yml" + } + + $verbose = if ($VerbosePreference -eq 'Continue') { + '--verbose' + } + + write-verbose "Running tests in '$path'" + docker-compose -f "$path" $verbose run $no_tty --rm test -c Invoke-Pester C:\Tests -EnableExit -OutputFile C:\Tests\Results.xml -OutputFormat NUnitXml + if (-not $?) { + $Failed = $true + } + + if ($env:APPVEYOR_JOB_ID) { + [string] $path = resolve-path "$PSScriptRoot\..\docker\Tests\Results.xml" + $url = "https://ci.appveyor.com/api/testresults/nunit/${env:APPVEYOR_JOB_ID}" + + write-verbose "Uploading '$path' to '$url'" + $wc = new-object System.Net.WebClient + $wc.UploadFile($url, $path) + } + } + + if ($Failed) { + exit 1 + } +}