Jenkins integration to calculate code coverage of WCF services hosted in IIS.

This blog will explain you how to automate the code coverage process of WCF services hosted in IIS with Jenkins.

Before we start…
For basic understanding see my previous blog : How to calculate code coverage of IIS hosted WCF services?

Here we go…

1. Lets assume you have already deployed your application in IIS. (Should be in ‘debug’ mode to calculate code coverage)

2. Create new Jenkins job.

3.  Add following build parameters.

Note – Do not change the names of following listed parameters as those are referenced in below given powershell scripts.

String parameters-:

  • AppPool (Value should be your service’s app pool name )
  • AppPoolIdentity (Value should be account/username under which your service’s app pool runs )
  • WebSiteBinDirectory (Value should be your service’s bin directory path )
  • EndPointUrl (Value should be your service’s endpoint url.  Example : dev.abc.com/myservice.svc )

Text parameter-:

  • Instrument (Value should be comma separated list of dll names which should be instrumented. )

4. Now, add Build step as Windows Powershell and copy paste below given powershell script without change. (This command will instrument all dlls listed above and start coverage capturing service)

param([bool]$deleteInstrumentedAssemblies)
#set variables:
$deleteInstrumentedAssemblies= 0
$solutionFriendlyName = 'MySolution'
$wakeUpServiceUrl = $env:EndPointUrl
$appPoolName = $env:AppPool
$appPoolIdentity = $env:AppPoolIdentity
$websiteBinDirectory = $env:WebSiteBinDirectory
$coverageOutputPath = "Test.coverage"
$instrument = $env:Instrument
$workspace = $env:Workspace

#-------------------------------------
# Function to get the running app pool 
#-------------------------------------
function get-apppool{
 [regex]$pattern="-ap ""($appPoolName)"""
 gwmi win32_process -filter 'name="w3wp.exe"' | % {
 $name=$_.name
 $cmd = $pattern.Match($_.commandline).Groups[1].Value
 $procid = $_.ProcessId
 New-Object psobject | Add-Member -MemberType noteproperty -PassThru Name $name |
 Add-Member -MemberType noteproperty -PassThru AppPoolID $cmd |
 Add-Member -MemberType noteproperty -PassThru PID $procid 
 }
}
#---------------------------------------
# Function to get the id of the app pool 
#---------------------------------------
function get-apppoolpid{
 $appPoolPid = 0
 $appPools = get-apppool
 foreach ($appPool in $appPools){
 if ($appPool.AppPoolID -eq "$appPoolName"){
 $appPoolPid = $appPool.PID
 }
 }
 write-host "$solutionFriendlyName app pool PID: $appPoolPID"
 return $appPoolPid
}
#------------------------------------------------
# Starts the app pool by making a service request
#------------------------------------------------
function start-apppool{
 #ping the service to start a new app pool process:
 $uri = new-object System.Uri("$wakeUpServiceUrl")
 $client = new-object System.Net.WebClient
 $client.DownloadString($uri) | out-null
}
#-------------------
# Kills the app pool
#-------------------
function kill-apppool{
 $procId = get-apppoolpid
 if ($procId -ne 0){
 write-host "Killing app pool process"
 $proc = [System.Diagnostics.Process]::GetProcessById($procId)
 $proc.Kill() 
 }
}
#-----------------------------------
# Starts instrumenting W3WP app pool
#------------------------------------
function start-instrumentation{
 #set instrumentation on:
 #restart the app pool and store the ID:
 kill-apppool
 start-apppool
 $procId = get-apppoolpid

write-host "Process id to attatch = $procId"
write-host "Starting VSperf cmd "

 & 'C:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Performance Tools\x64\vsperfcmd' /START:COVERAGE /OUTPUT:$coverageOutputPath /CS /USER:$appPoolIdentity
 kill-apppool 
 start-apppool
 & 'C:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Performance Tools\x64\vsperfcmd' /status
write-host "Done VSperf cmd "
}
#--------------
# Script begins
#--------------
write-host "Stopping iis..."
&iisreset /stop
write-host "starting - instrumenting assemblies"
$content = $instrument.Split(",")
write-host "Content after split" $content
 $cmd = "C:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Performance Tools\x64\vsinstr.exe"
 write-host "Executing $cmd /coverage for all dlls" 
 $fullpath =""
 $string=""
 foreach ($dll in $content){
 $string = "$websiteBinDirectory\$dll" 
 $string = $string -replace "`t|`n|`r",""
 Write-Host "instrumenting dll = " $cmd /coverage $string
 & $cmd /coverage $string
 $string=""
 }
write-host "Done - instrumenting assemblies"
write-host "Starting iis..."
&iisreset /start
write-host "Before starting instrumentation, last exit code: $LASTEXITCODE"
#instrument W3WP, run tests & export results:
start-instrumentation
write-host "Before running tests, last exit code: $LASTEXITCODE"
write-host "Before existing, last exit code: $LASTEXITCODE, real exit code: $realExitCode"
exit $realExitCode
#------------
# Script ends
#------------

5. Add MsTest or VsTest block which will run service test.

6. Now, again add Build step as Windows Powershell and copy paste below given powershell script without change. (This script will stop the coverage capturing service and converts .coverage file to .coveragexml which can be used then with other tools like ReportGenerator to view report in Jenkins. )

param([bool]$deleteInstrumentedAssemblies)
#set variables:
$deleteInstrumentedAssemblies= 0
$solutionFriendlyName = 'MySolution'
$wakeUpServiceUrl = $env:EndPointUrl
$appPoolName = $env:AppPool
$appPoolIdentity = $env:AppPoolIdentity
$websiteBinDirectory = $env:WebSiteBinDirectory
$coverageOutputPath = "Test.coverage"
$instrument = $env:Instrument
$workspace = $env:Workspace
#-------------------------------------
# Function to get the running app pool 
#-------------------------------------
function get-apppool{
 [regex]$pattern="-ap ""($appPoolName)"""
 gwmi win32_process -filter 'name="w3wp.exe"' | % {
 $name=$_.name
 $cmd = $pattern.Match($_.commandline).Groups[1].Value
 $procid = $_.ProcessId
 New-Object psobject | Add-Member -MemberType noteproperty -PassThru Name $name |
 Add-Member -MemberType noteproperty -PassThru AppPoolID $cmd |
 Add-Member -MemberType noteproperty -PassThru PID $procid 
 }
}
#---------------------------------------
# Function to get the id of the app pool 
#---------------------------------------
function get-apppoolpid{
 $appPoolPid = 0
 $appPools = get-apppool
 foreach ($appPool in $appPools){
 if ($appPool.AppPoolID -eq "$appPoolName"){
 $appPoolPid = $appPool.PID
 }
 }
 write-host "$solutionFriendlyName app pool PID: $appPoolPID"
 return $appPoolPid
}
#------------------------------------------------
# Starts the app pool by making a service request
#------------------------------------------------
function start-apppool{
 #ping the service to start a new app pool process:
 $uri = new-object System.Uri("$wakeUpServiceUrl")
 $client = new-object System.Net.WebClient
 $client.DownloadString($uri) | out-null
}
#-------------------
# Kills the app pool
#-------------------
function kill-apppool{
 $procId = get-apppoolpid
 if ($procId -ne 0){
 write-host "Killing app pool process"
 $proc = [System.Diagnostics.Process]::GetProcessById($procId)
 $proc.Kill() 
 }
}
#--------------------
# Stops instrumenting
#--------------------

function stop-instrumentation{
 #stop instrumenting & reset app pool:
 & 'C:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Performance Tools\x64\vsperfcmd' /DETACH
 kill-apppool
 & 'C:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Performance Tools\x64\vsperfcmd' /SHUTDOWN 
}
#------------------------
# Exports coverage to XML
#------------------------
function export-coverage{
 #export the coverage file to XML:
 [System.Reflection.Assembly]::LoadFile("C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\PrivateAssemblies\Microsoft.VisualStudio.Coverage.Analysis.dll")
 $coverage = [Microsoft.VisualStudio.Coverage.Analysis.CoverageInfo]::CreateFromFile("$coverageOutputPath")
 $dataSet = $coverage.BuildDataSet()
 $dataSet.WriteXml("$coverageOutputPath" + 'xml')
}
#----------------------------------------------------------------------------------
# Deletes instrumented assemblies, and reinstates original un-instrumented versions
#----------------------------------------------------------------------------------
function delete-intstrumentedassemblies{
 $dir = new-object IO.DirectoryInfo($websiteBinDirectory)
 $originalAssemblies = $dir.GetFiles("*.dll.orig")
 foreach ($originalAssembly in $originalAssemblies){
 $targetName = $originalAssembly.FullName.TrimEnd(".orig".ToCharArray())
 #overwrite the instrumented DLL with the original:
 [IO.File]::Copy($originalAssembly.FullName, $targetName, $true)
 [IO.File]::Delete($originalAssembly.FullName)
 #delete the instrumented PDB:
 $instrumentedPdbName = $originalAssembly.FullName.TrimEnd(".dll.orig".ToCharArray()) + ".instr.pdb"
 [IO.File]::Delete($instrumentedPdbName)
 }
}
#--------------
# Script begins
#--------------
write-host "Stopping iis..."
&iisreset /stop
stop-instrumentation
export-coverage
if ($deleteInstrumentedAssemblies -eq $true){
 delete-intstrumentedassemblies
}
write-host "Starting iis..."
&iisreset /start
write-host "Before existing, last exit code: $LASTEXITCODE, real exit code: $realExitCode"
exit $realExitCode
#------------
# Script ends
#------------

7. Use the .coveragexml obtained from above step and use code coverage report generation tools of your choice to display report in Jenkins itself.(Such as ReportGenerator – https://reportgenerator.codeplex.com/ )

Note : Above steps/powershell scripts will only work if Jenkins server and deployment server are SAME. i.e.Both should be on single machine.

Whats Nxt???

Knowing the above limitation of SAME machine, need arises to write powershell script that can function as above but with Jenkins running on one machine and application deployed on another machine.
Windows Remote Management (WinRM) can be helpful for this.

Happy coding…

One thought on “Jenkins integration to calculate code coverage of WCF services hosted in IIS.

Leave a comment