2016-01-17 17:17:56 +00:00
function Invoke-MsTest
{
<#
. SYNOPSIS
Run tests for a given Visual Studio solution or project using MsTest
. DESCRIPTION
Parses each Visual Studio solution or project and extracts test containers .
Test each file using MsTest . exe to create a single . trx file .
Optionally user may choose to only run specific test names .
Optionally user may view or keep the MsTest . exe command window open to view results .
. PARAMETER Path
The path of the Visual Studio solution or project to build ( e . g . a . sln or . csproj file ) .
. PARAMETER Tests
A string array with the test names to be ran .
By Default the script will run all test associated with the project or solution .
. PARAMETER Unique
Switch that will only run tests who ’ s name exactly matches those listed in Tests .
. PARAMETER ResultsDirectoryPath
The directory path to create the test result file ( . trx )
By Defaults the test file will be written to the users temp directory ( e . g . C: \ Users \ [ User Name ] \ AppData \ Local \ Temp ) .
. PARAMETER Verbose
Switch will print test information to the PowerShell console .
. PARAMETER ShowTestWindow
Switch will show the cmd window running MsTest . exe
. PARAMETER PromptToCloseTestWindow
Switch that gives users the option of keeping the cmd window running MsTest . exe open once all the tests are complete .
. PARAMETER NoResults
Switch will block the results object from returning ( Cannot be used with IsAllPassed ) .
. PARAMETER IsAllPassed
Switch will return a boolean true if all test pass else a false ( Cannot be used with NoResults ) .
. PARAMETER MsTestParameters
Additionally MsTest Parameters that the user may wish to include ( http : / / msdn . microsoft . com / en-US / library / ms182489 ( v = vs . 80 ) . aspx )
Note : This script uses / testcontainer to run tests and will not support / testmetadata parameters .
. OUTPUTS
If NoResults is not selected then the script will pass an object with Outcomes , Test names ( or project / solution names ) , Storage ( container dll ) , and Comments .
If all tests passed , there will be no output .
. EXAMPLE
Import-Module " C:\Path To Module\Inoke-MsTest.psm1 "
$TestResults = Invoke-MsTest -Path " C:\Some Folder\MySolution.sln "
Foreach ( $TestResult in $TestResults )
{
if ( $TestResult . Outcome -eq " Failed " )
{
Write-Host $TestResult . Name -ForgroundColor Red
}
}
The default test will run all test under a given Visual Studio solution or project and return a list of failed tests , project , or solutions .
The above example will print the name of all test failures to the console .
. EXAMPLE
$TestFailures = Invoke-MsTest -Path " C:\Some Folder\MySolution.sln " -Tests MyTest1 , MyTest2 , MyTest3
Listing tests will only run tests that match the test name given .
Note : MyTest1 will match with MyTest10 , MyTest11 , MyTest100 to use only exact matching test names use the 'Unique' switch
. EXAMPLE
Invoke-MsTest -Path " C:\Some Folder\MyProject.csproj " -ResultsDirectoryPath " C:\Some Folder "
Runs test and drops the test results file in the " C:\Some Folder " directory
. EXAMPLE
Invoke-MsTest -Path " C:\Some Folder\MyProject.csproj " -Verbose -NoResults
Runs test and print test projess / results to the console . This will not pass back the results object .
. EXAMPLE
Invoke-MsTest -Path " C:\Some Folder\MySolution.sln " -PromptToCloseTestWindow
Runs test and keep the cmd MsTest . exe window open awaiting user input .
. LINK
Project home : https : / / invokemstest . codeplex . com /
. NOTES
Name : Invoke-MsTest
Author : Paul Selles [ http : / / paulselles . wordpress . com / ]
Version : 2.0
Inspired by my colleague , Daniel Schroeder ' s Invoke-MsBuild [ https : / / invokemsbuild . codeplex . com ]
#>
[ CmdletBinding ( DefaultParameterSetName = " Wait " ) ]
param
(
# List the root path for your projects or solutions
# this will find all test containers within and run them
[ Parameter ( Position = 0 , Mandatory = $true , ValueFromPipeline = $true ) ]
[ ValidateScript ( { ( Test-Path $_ -PathType Leaf ) -and ( ( ( Dir $_ ) . Extension -eq " .sln " ) -or ( ( Dir $_ ) . Extension -match '.*..*proj' ) ) } ) ]
[ String[] ] $Path ,
# List of names of the specific test to run, if nothing is
# specified then run all tests in the test containers
[ parameter ( Mandatory = $false ) ]
[ ValidateNotNullOrEmpty ( ) ]
[ String[] ] $Tests = $null ,
# Will only run tests that exactly match the test name 'Tests' parameter
[ Parameter ( Mandatory = $false ) ]
[ Alias ( " U " ) ]
[ Switch ] $Unique ,
# Option to select the location of the result files
[ parameter ( Mandatory = $false ) ]
[ ValidateScript ( { Test-Path $_ -PathType Container } ) ]
[ Alias ( " Results " ) ]
[ Alias ( " R " ) ]
[ string ] $ResultsDirectoryPath = $env:Temp ,
# Show the tests running in a command window
[ parameter ( Mandatory = $false ) ]
[ Alias ( " Show " ) ]
[ Alias ( " S " ) ]
[ switch ] $ShowTestWindow ,
[ parameter ( Mandatory = $false ) ]
[ Alias ( " Prompt " ) ]
[ switch ] $PromptToCloseTestWindow ,
# Will not return test results
[ parameter ( Mandatory = $false , ParameterSetName = " NoResults " ) ]
[ Switch ] $NoResults ,
# Will return a boolean result, $true if all tests pass otherwise $false
[ parameter ( Mandatory = $false , ParameterSetName = " IsAllPassed " ) ]
[ Alias ( " BoolResults " ) ]
[ Switch ] $IsAllPassed ,
# Desired test run parameters
[ parameter ( Mandatory = $false ) ]
[ ValidateNotNullOrEmpty ( ) ]
[ Alias ( " Params " ) ]
[ Alias ( " P " ) ]
[ string ] $MsTestParameters
)
BEGIN { }
END { }
PROCESS
{
Set-StrictMode -Version Latest
foreach ( $PathIteration in $Path ) {
# Array for our test results
$TestResults = @ ( )
# Sets the verbose Parameter
if ( $PSBoundParameters [ 'Verbose' ] ) { $Verbose = $true }
else { $Verbose = $false }
# Get the path to the MsTest executable
# Exit if MsTest path is invalid
$MsTest = Get-MsTest
if ( -not ( Test-Path $MsTest ) ) { return }
# Array for out container and projects
$TestContainers = @ ( )
$Projects = @ ( )
# Get a list of project under the supplied solution file
if ( ( ( Dir $PathIteration ) . Extension -eq " .sln " ) )
{
Get-Content -Path $PathIteration | % {
if ( $_ -match '\s*Project.+=\s*.*,\s*\"\s*(.*proj)\s*\"\s*,\s*' ) {
$Projects + = Join-Path -Path ( Dir $PathIteration ) . DirectoryName $matches [ 1 ]
}
}
}
# If a project file we don't need to so much
elseif ( ( Dir $PathIteration ) . Extension -match '.*..*proj' )
{
$Projects + = $PathIteration
}
# Loop through all project files and
Foreach ( $Project in $Projects )
{
$ProjectXml = [ Xml ] ( Get-Content -Path $Project )
$ns = New-Object Xml . XmlNamespaceManager $ProjectXml . NameTable
$ns . AddNamespace ( 'dns' , 'http://schemas.microsoft.com/developer/msbuild/2003' )
# Test is the project includes the unit test class
if ( [ Bool ] ( $ProjectXml . SelectNodes ( 'dns:Project//dns:ItemGroup/dns:Reference' , $ns ) | Where { $_ . Include -match 'Microsoft.VisualStudio.QualityTools.UnitTestFramework' } ) )
{
# Collect all possible configurations
$Configurations = $ProjectXml . SelectNodes ( 'dns:Project/dns:PropertyGroup[@Condition]' , $ns ) . Condition
# Collect all PropertyGroup Nodes referencing IntermediateOutputPath and BaseIntermediateOutputPath elements
$IntermediateOutputPathNodes = $ProjectXml . SelectNodes ( '//dns:Project/dns:PropertyGroup[dns:IntermediateOutputPath]' , $ns )
$BaseIntermediateOutputPathNodes = $ProjectXml . SelectNodes ( '//dns:Project/dns:PropertyGroup[dns:BaseIntermediateOutputPath]' , $ns )
# Container for the IntermediateOutputPath relative to the proj directory
$IntermediateOutputPaths = @ ( )
# IntermediateOutputPath take precedence over BaseIntermediateOutputPath
# Remove all BaseIntermediateOutputPaths that matches with an IntermediateOutputPath Configuration
# Register the IntermediateOutputPath
# Remove Configuration from Configurations list
Foreach ( $IntermediateOutputPathNode in $IntermediateOutputPathNodes )
{
$Configuration = $IntermediateOutputPathNode . Condition
$BaseIntermediateOutputPathNodes = $BaseIntermediateOutputPathNodes | ? { $_ . GetAttribute ( 'Condition' ) -ne $Configuration }
$IntermediateOutputPaths + = $IntermediateOutputPathNode . IntermediateOutputPath
$Configurations = $Configurations | ? { $_ -ne $Configuration }
}
# Register the remaining BaseIntermediateOutputPath
Foreach ( $BaseIntermediateOutputPathNode in $BaseIntermediateOutputPathNodes )
{
$Configuration = $IntermediateOutputPathNode . Condition
$IntermediateOutputPaths + = $BaseIntermediateOutputPathNode . BaseIntermediateOutputPath
$Configurations = $Configurations | ? { $_ -ne $Configuration }
}
$IntermediateOutputPaths = $IntermediateOutputPaths | % { Join-Path ( Dir $Project ) $_ } | Where { Test-Path " $_ \*FileListAbsolute.txt " }
if ( $Configurations ) {
$IntermediateOutputPaths + = ( Get-ChildItem -Path ( Join-Path ( Dir $Project ) . DirectoryName obj ) -Directory ) . FullName | Where { Test-Path " $_ \*FileListAbsolute.txt " }
}
# If all IntermediateOutputPaths are empty alert user
if ( -not $IntermediateOutputPaths )
{
if ( $Verbose ) { Write-Host " Error : " ( Dir $Project ) . Name " : Could not find IntermediateOutputPath contents. Please rebuild the solution/project and try again. " -ForegroundColor Red }
$TestResults + = New-Object PSObject -Property @ { Outcome = " projerror " ; Name = ( Dir $Project ) . Name ; Storage = " " ; Comments = " Could not find IntermediateOutputPath contents. Please rebuild the solution/project and try again. " }
break
}
# Get the latest FileListAbsolute this is cumbersome because if it is an array of one element the string will return the first char
$FileListAbsolute = ( ( $IntermediateOutputPaths | % { ( Get-ChildItem -Path $_ -File * . FileListAbsolute . txt ) } | Sort -Descending { ( Dir $_ . FullName ) . LastWriteTime } ) [ 0 ] ) . FullName
# Find all test containers from the DLL files submitted
$ProjectTestContainers = @ ( )
# Get a list of all the DLL in the project and find the test containers
Get-Content -Path $FileListAbsolute | % { if ( $_ -match '.*\\bin\\.*.dll' -and $_ -notmatch '.*nunit.core.dll' ) {
if ( -not ( Test-Path $_ ) )
{
if ( $Verbose ) { Write-Host " Error : " ( Dir $Project ) . Name " : Could not find library $_ . Please rebuild the solution/project and try again. " -ForegroundColor Red }
$TestResults + = New-Object PSObject -Property @ { Outcome = " projerror " ; Name = ( Dir $Project ) . Name ; Storage = " $_ " ; Comments = " Could not find library $_ . Please rebuild the solution/project and try again. " }
}
elseif ( [ IO.File ] :: ReadAllText ( $_ ) -match 'TestClass' )
{
$ProjectTestContainers + = $_
}
} }
# If there are not test containers, let the user know
if ( -not $ProjectTestContainers )
{
if ( $Verbose ) { Write-Host " Warning : " ( Dir $Project ) . Name " : Could not find test containers. " -ForegroundColor Yellow }
$TestResults + = New-Object PSObject -Property @ { Outcome = " projwarning " ; Name = ( Dir $Project ) . Name ; Storage = " " ; Comments = " Could not find test containers. " }
}
else
{
if ( $Verbose ) { Write-Host " Added : " ( Dir $Project ) . Name " : Found " $ProjectTestContainers . Count " test container(s) " -ForegroundColor Green }
}
$TestContainers + = $ProjectTestContainers
}
}
if ( -not $TestContainers )
{
# By returning the unique TestResults we prevent spamming the same problem (ie. Multiple assemblies cannot be found with only report once)
if ( $Verbose ) { Write-Host " Error : " ( Dir $PathIteration ) . Name " : Could not find any test containers. " -ForegroundColor Red }
$TestResults + = New-Object PSObject -Property @ { Outcome = " testerror " ; Name = ( Dir $PathIteration ) . Name ; Storage = " " ; Comments = " Could not find any test containers. " }
if ( $IsAllPassed ) { ( ! $TestResults ) }
elseif ( ! $NoResults ) { $TestResults }
return
}
# Add Parameters to test arguments
$TestArguments = " ${MsTestParameters} "
# Add test names the arguments and unique, if chosen
if ( $Tests )
{
$TestArguments = " ${TestArguments} /test: "
foreach ( $Test in $Tests ) { $TestArguments = " ${TestArguments} ${Test} , " }
$TestArguments = $ ( $TestArguments . Substring ( 0 , $TestArguments . Length - 1 ) )
if ( $Unique ) { $TestArguments = " ${TestArguments} /unique " }
}
foreach ( $TestContainer in $TestContainers )
{
if ( $Verbose ) { Write-Host " Loading Test: " ( Dir $TestContainer ) . BaseName -ForegroundColor Cyan }
$TestArguments = " ${TestArguments} /testcontainer: ${TestContainer} "
}
# Select our window style
$WindowStyle = if ( $ShowTestWindow -or $PromptToCloseTestWindow ) { " Normal " } else { " Hidden " }
# Time Stamp will be added to our test files
$TimeStamp = ( Get-Date -Format " yyyy-MM-dd hh_mm_ss " )
# Add the test results file to the directory
$TestResultsFile = " InvokeMsTestResults ${TimeStamp} .trx "
$TestResultsFile = Join-Path $ResultsDirectoryPath $TestResultsFile
$TestArguments = " ${TestArguments} /resultsfile: `" ${TestResultsFile} `" "
# Construct the test test cmd argument
$PauseForInput = if ( $PromptToCloseTestWindow ) { " Pause & " } else { " " }
$TestCmdArgument = " /k "" "" ${MsTest} "" ${TestArguments} & ${PauseForInput} Exit "" "
# Starts the MsTests
Start-Process cmd . exe -ArgumentList $TestCmdArgument -WindowStyle $windowStyle -Wait
if ( Test-Path $TestResultsFile )
{
$TestResultsFileXml = [ Xml ] ( Get-Content -Path $TestResultsFile )
$ns = New-Object Xml . XmlNamespaceManager $TestResultsFileXml . NameTable
$ns . AddNamespace ( 'dns' , 'http://microsoft.com/schemas/VisualStudio/TeamTest/2010' )
$UnitTests = $TestResultsFileXml . SelectNodes ( '//dns:TestRun/dns:TestDefinitions/dns:UnitTest' , $ns )
$UnitTestResults = $TestResultsFileXml . SelectNodes ( '//dns:TestRun/dns:Results/dns:UnitTestResult' , $ns )
if ( $Verbose ) { Write-Host " -------------------------------------------------------------------------------- `r `n Results `r `n -------------------------------------------------------------------------------- " }
Foreach ( $UnitTestResult in $UnitTestResults )
{
$Storage = ( $UnitTests | Where { $_ . id -eq $UnitTestResult . testId } ) . storage
$TestResults + = New-Object PSObject -Property @ { Outcome = $UnitTestResult . outcome ; Name = $UnitTestResult . testName ; Storage = $Storage ; Comments = " " }
if ( $Verbose )
{
if ( $UnitTestResult . outcome -eq " passed " ) { $Color = 'Green' }
elseif ( $UnitTestResult . outcome -eq " warning " ) { $Color = 'Yellow' }
elseif ( $UnitTestResult . outcome -eq " failed " ) { $Color = 'Red' }
else { $Color = 'Gray' }
Write-Host $UnitTestResult . outcome " `t " $UnitTestResult . testName " `t " $Storage -ForegroundColor $Color
}
}
}
else
{
if ( $Verbose ) { Write-Host " Error: Could not find or open test results file. " }
$TestResults + = New-Object PSObject -Property @ { Outcome = " testerror " ; Name = ( Dir $PathIteration ) . Name ; Storage = " " ; Comments = " Could not find or open test results file. " }
}
if ( $IsAllPassed ) { ( ( $TestResults . outcome | Where { $_ -eq " Passed " } ) . Count -eq $TestResults . Count ) }
elseif ( ! $NoResults ) { $TestResults }
}
}
}
################################################################################
function Get-MsTest
{
<#
. SYNOPSIS
Gets path for latest version of MsTest . exe
. DESCRIPTION
Gets path for latest version of MsTest . exe
#>
$MsTest = " $( Get-VsCommonTools ) ..\IDE\MsTest.exe "
if ( Test-Path $MsTest ) { $MsTest }
else { Write-Error " Unable to find MsTest.exe " }
}
################################################################################
function Get-VsCommonTools
{
<#
. SYNOPSIS
Gets path to the current VS common tools
. DESCRIPTION
Gets path to the current VS common tools .
2016-02-07 07:51:57 +00:00
Current list supports VS14 , VS12 , VS11 and VS10 , you may need to add to this list
2016-01-17 17:17:56 +00:00
to satisfy your needs .
#>
2016-02-07 07:51:57 +00:00
$VsCommonToolsPaths = @ ( @ ( $env:VS140COMNTOOLS , $env:VS120COMNTOOLS , $env:VS110COMNTOOLS , $env:VS100COMNTOOLS ) | Where-Object { $_ -ne $null } )
2016-01-17 17:17:56 +00:00
if ( $VsCommonToolsPaths . Count -ne 0 ) { $VsCommonToolsPaths [ 0 ] }
else { Write-Error " Unable to find Visual Studio Common Tool Path. " }
}
################################################################################
Export-ModuleMember -Function Invoke-MsTest , Get-MsTest