Skip to content

Instantly share code, notes, and snippets.

@jwatney
Created July 12, 2017 18:57
Show Gist options
  • Save jwatney/e450e07ce39e75bcd73ae5925e8a5e02 to your computer and use it in GitHub Desktop.
Save jwatney/e450e07ce39e75bcd73ae5925e8a5e02 to your computer and use it in GitHub Desktop.
# This script runs our web-based tests, running IISExpress via TeamCity's
# dotCover, then runing the tests (also in dotCover) and then importing the
# test results and coverage results back into TeamCity.
# Note that some of the following is based on the comments by Tony Fabris on
# 20th Jan 2016 from here: https://youtrack.jetbrains.com/issue/DCVR-5921
# Configuration.
# The path to IIS Express.
$IISExpressPath = "C:\Program Files (x86)\IIS Express\IISExpress.exe"
# The path to dotCover, the TeamCity tool for .NET code coverage.
$DotCoverPath = "C:\TeamCity\buildAgent\tools\dotCover\dotcover.exe"
# The path to the VSTest console.
$VSTestConsolePath = "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
# The path to PSExec.
$PSExecPath = "C:\path\to\PSTools\PSExec.exe"
# The path of the website which will be hosted in IIS express.
$WebsitePath = "C:\path\to\webapp"
# The port on which to host the website.
$WebsitePort = 12345
# The path to use as the working directory when running the tests.
# Note that the test results are written to a .trx file in a TestResults
# subdirectory of this directory.
$TestWorkingPath = "c:\path\to\checkout\dir"
# The assembly to run tests for.
$TestAssembly = "C:\path\to\test.dll"
# Get the TEMP folder.
# When run from the command-line, this will be something like the user's AppData\Local\Temp
# When run from TeamCity, this will be something like C:\TeamCity\buildAgent\temp\buildTemp
$tempDir = (Get-Item $Env:TEMP).FullName
Write-Host ("tempDir: " + $tempDir)
# Work out the output directory to write various output files to, in a
# CustomCoverage subdirectory of the temporary directory.
$outputDir = Join-Path $tempDir "CustomCoverage"
Write-Host ("outputDir: " + $outputDir)
# Create that output directory, but only if it doesn't exist.
if (!(Test-Path $outputDir))
{
md $outputDir
}
# Work out the path of the dotcover log file used for IISExpress, and delete it
# if it already exists.
$dotCoverIISExpressLogFilename = "$outputDir\dotcover.iisexpress.log.txt"
If (Test-Path $dotCoverIISExpressLogFilename)
{
del $dotCoverIISExpressLogFilename
}
# Work out the path of the dotCover output file used for IISExpress.
$dotCoverIISExpressOutputFilename = "$outputDir\dotcover.iisexpress.dcvr"
# Work out the arguments that we'll pass to IIS Express.
$IISExpressArgs = "/path:$WebsitePath /port:$WebsitePort /trace:info"
# Work out the arguments to pass to dotCover to start and cover IIS Express.
# Because the arguments to dotCover are long, we write them out to a config file.
$dotCoverIISExpressConfigFilename = "$outputDir\dotcover.iisexpress.config.xml"
"<?xml version=`"1.0`" encoding=`"us-ascii`"?>`n" + `
"<CoverageParams>`n" + `
" <LogFile>$dotCoverIISExpressLogFilename</LogFile>`n" + `
" <Output>$dotCoverIISExpressOutputFilename</Output>`n" + `
" <TargetExecutable>$IISExpressPath</TargetExecutable>`n" + `
" <TargetArguments>$IISExpressArgs</TargetArguments>`n" + `
"</CoverageParams>`n" `
| Out-File -Encoding ASCII $dotCoverIISExpressConfigFilename
# Write the command to execute dotCover out to a .bat file.
# Within the batch file we redirect stdout and stderr to a file, so we can see
# any reasons for failure.
# We put this in a batch file because it is too long for PSExec, which is
# limited to 260 characters for a single argument.
# http://forum.sysinternals.com/psexec-argument-to-long_topic14203.html
$dotCoverIISExpressRunFilename = "$outputDir\dotcover.iisexpress.run.bat"
"`"$DotCoverPath`" cover `"$dotCoverIISExpressConfigFilename`" > `"$outputDir\iisexpress.stdout.txt`" 2> `"$outputDir\iisexpress.stderr.txt`"" | Out-File -Encoding ASCII $dotCoverIISExpressRunFilename
# We need to run IISExpress (hosted in dotCover) in an interactive session, in
# order to be able to send it a WM_QUIT to shut it down gracefully after the
# tests (or else no coverage data will be recorded).
# We do this by running dotCover via PSExec.
# -i : interactive session
# -h : use the account's elevated token
# -accepteula : accept the pstools EULA, as the user running the build agent probably won't have
$psExecArgs = "-i -h -accepteula `"$dotCoverIISExpressRunFilename`""
# Start PSExec -> DotCover -> IISExpress.
# This will block, so we use Start-Process so that's it's done in a separate
# process, and so this script will continue.
# The -PassThru argument is required so that Start-Process will return a process
# handle.
$IISExpressProcess = (Start-Process $PSExecPath $psExecArgs -PassThru)
# Work out the path of the dotcover log file used for VSTest, and delete it
# if it already exists.
$dotCoverVSTestLogFilename = "$outputDir\dotcover.vstest.log.txt"
If (Test-Path $dotCoverVSTestLogFilename)
{
del $dotCoverVSTestLogFilename
}
# Work out the path of the dotCover output file used for VSTest.
$dotCoverVSTestOutputFilename = "$outputDir\dotcover.vstest.dcvr"
# Work out the arguments to pass to VSTest console.
# The /Logger:trx argument outputs the results in MSTest format, which TeamCity
# is able to import.
$vsTestArgs = "`"$TestAssembly`" /Logger:trx"
# Then run dotCover to run VSTest.
# This will run the tests, and also collect code coverage for the tests
# themselves.
# We expect the tests to be making calls to the website, which is running on IIS
# Express and being covered that way.
& $DotCoverPath cover /LogFile="$dotCoverVSTestLogFilename" /Output="$dotCoverVSTestOutputFilename" /TargetExecutable="$VSTestConsolePath" /WorkingDir="$TestWorkingPath" /TargetArguments="$vsTestArgs"
# We now need to shut down IISExpress.
# This has to be done gracefully, or else coverage data won't be collected.
# The core command to do this is 'taskkill /IM IISExpress.exe'.
# Note that there's no /F parameter to force - so what this will do is send a
# WM_QUIT to the process, to tell it to shut down.
# However, this only works if IISExpress was run in an interactive session (-i),
# and also if this taskkill call is as well.
# Both also require the -h parameter (elevated token) to be able to work.
# Note that we also want to log the output of taskkill, to help track down any
# issues.
# If we just do this, then powershell handles the redirection to the file:
# & PSExec taskkill > output.txt
# If we add a ` to escape the >, then powershell doesn't do the redirection, but
# we still end up redirecting the output of PSExec, instead of the output of
# taskkill.
# & PSExec taskkill `> output.txt
# Using 'cmd /C' instead allows us to pass the command in quotes, and therefore
# the redirect applies to the taskkill, instead of to the PSExec:
# & PSExec cmd / C "taskkill `> output.txt"
# We log the output of stdout (via >) and stderr (via 2>).
# So stop IIS Express.
& $PSExecPath -i -h -accepteula cmd /C `"taskkill /IM IISExpress.exe `> "$outputDir\taskkill.stdout.txt" 2`> "$outputDir\taskkill.stderr.txt"`"
# As a record, write out the contents of the stdout and stderr log files, for
# both IIS Express and taskkill, to help track down any issues.
# Note that IIS Express will have been logging during execution of the tests, so
# we can't collect the log until we've killed it.
Write-Host "-- iis express stdout --"
Get-Content "$outputDir\iisexpress.stdout.txt"
Write-Host "------------------------"
Write-Host "-- iis express stderr --"
Get-Content "$outputDir\iisexpress.stderr.txt"
Write-Host "------------------------"
Write-Host "-- taskkill stdout -----"
Get-Content "$outputDir\taskkill.stdout.txt"
Write-Host "------------------------"
Write-Host "-- taskkill stderr -----"
Get-Content "$outputDir\taskkill.stderr.txt"
Write-Host "------------------------"
# IIS Express takes a little while to close, the dotCover which is wrapping it
# then takes a little while to write out the results. This is all wrapped up in
# PSExec. Wait for this chain of processes to exit.
Write-Host "Waiting for IIS Express to close..."
$IISExpressProcess.WaitForExit()
Write-Host "- done"
# Import the test results in the .trx file in MSTest format.
# Note that this path is relative to the checkout directory, and that VSTest
# writes the output into the TestResults subdirectory of the working directory
# it was invoked with.
Write-Host "##teamcity[importData type='mstest' path='TestResults\*.trx']"
# These commands create an XML report from the dotCover coverage files.
# This isn't necessary, as TeamCity will handle merging the coverage files and
# then generating the report.
# However it may be useful to uncomment these for testing.
#& $DotCoverPath report /Source:$dotCoverIISExpressOutputFilename /Output:$outputDir\dotcover.iisexpress.report.xml /ReportType:xml
#& $DotCoverPath report /Source:$dotCoverVSTestOutputFilename /Output:$outputDir\dotcover.vstest.report.xml /ReportType:xml
# Write a service message to make TeamCity import code coverage from IIS express.
# Note that TeamCity will delete this file once it has processed it.
Write-Host "##teamcity[importData type='dotNetCoverage' tool='dotcover' path='$dotCoverIISExpressOutputFilename']"
# Write a service message to make TeamCity import code coverage from VSTest.Console.
# Note that TeamCity will delete this file once it has processed it.
Write-Host "##teamcity[importData type='dotNetCoverage' tool='dotcover' path='$dotCoverVSTestOutputFilename']"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment