# encoding: ascii # api: powershell # title: WCF code coverage # description: Script for running unit tests over WCF services to get code coverage for the whole service stack # version: 10.0 # type: script # author: sixeyed # license: CC0 # function: get-apppool # x-poshcode-id: 3141 # x-archived: 2012-02-01T10:20:01 # x-published: 2012-01-04T10:33:00 # # param($msBuildTarget, $configurationName, [bool]$deleteInstrumentedAssemblies) #------------------------------------- # Script to compile coverage for a WCF # solution running in IIS. # See: # http://geekswithblogs.net/EltonStoneman/archive/2011/10/14/end-to-end-wcf-code-coverage-with-powershell.aspx #------------------------------------- #------------------------------------- # 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() } } #------------------------------------------------------------------------------ # Instruments an assembly for code coverage, excluding the specified namespaces #------------------------------------------------------------------------------ function instrument2([string]$assemblyName, [string[]]$excludes){ $assy = "$websiteBinDirectory\$assemblyName" $excludeLine = "" if ($excludes -ne $null){ foreach ($x in $excludes){ write-host "Excluding: $x.*" $excludeLine = "$excludeLine-exclude:$x.* " } } $cmd = "C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe" write-host "Executing $cmd /coverage $excludeLine $assy" & $cmd /coverage "$excludeLine" "$assy" } #------------------------------------------------------------------------------ # Instruments an assembly for code coverage, excluding the specified namespaces #------------------------------------------------------------------------------ function instrument([string]$assemblyName, [string[]]$excludes){ $assy = "$websiteBinDirectory\$assemblyName" #ES - this doesn't work as the ":" in exlucde gets parsed out by PS: #$excludeParms = "" #$args #foreach($exclude in $args){ # $excludeParms = $excludeParms + '/exclude:' + $exclude + '.* ' #} #$excludeParms #& 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe' /coverage $excludeParms $assy if ($excludes -eq $null){ write-host "Excluding 0 funcspecs" & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe' /coverage $assy } elseif ($excludes.Length -eq 0){ write-host "Excluding 0 funcspecs" & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe' /coverage $assy } elseif ($excludes.Length -eq 1){ write-host "Excluding 1 funcspecs" $exclude1 = '-exclude:' + $excludes[0] & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe' /coverage $exclude1 $assy } elseif ($excludes.Length -eq 2){ write-host "Excluding 2 funcspecs" $exclude1 = '-exclude:' + $excludes[0] $exclude2 = '-exclude:' + $excludes[1] & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe' /coverage $exclude1 $exclude2 $assy } elseif ($excludes.Length -eq 3){ write-host "Excluding 3 funcspecs" $exclude1 = '-exclude:' + $excludes[0] $exclude2 = '-exclude:' + $excludes[1] $exclude3 = '-exclude:' + $excludes[2] & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe' /coverage $exclude1 $exclude2 $exclude3 $assy } elseif ($excludes.Length -eq 4){ write-host "Excluding 4 funcspecs" $exclude1 = '-exclude:' + $excludes[0] $exclude2 = '-exclude:' + $excludes[1] $exclude3 = '-exclude:' + $excludes[2] $exclude4 = '-exclude:' + $excludes[3] & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe' /coverage $exclude1 $exclude2 $exclude3 $exclude4 $assy } elseif ($excludes.Length -eq 5){ write-host "Excluding 5 funcspecs" $exclude1 = '-exclude:' + $excludes[0] $exclude2 = '-exclude:' + $excludes[1] $exclude3 = '-exclude:' + $excludes[2] $exclude4 = '-exclude:' + $excludes[3] $exclude5 = '-exclude:' + $excludes[4] & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe' /coverage $exclude1 $exclude2 $exclude3 $exclude4 $exclude5 $assy } elseif ($excludes.Length -eq 6){ write-host "Excluding 6 funcspecs" $exclude1 = '-exclude:' + $excludes[0] $exclude2 = '-exclude:' + $excludes[1] $exclude3 = '-exclude:' + $excludes[2] $exclude4 = '-exclude:' + $excludes[3] $exclude5 = '-exclude:' + $excludes[4] $exclude6 = '-exclude:' + $excludes[5] & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsinstr.exe' /coverage $exclude1 $exclude2 $exclude3 $exclude4 $exclude5 $exclude6 $assy } } #----------------------------------- # Starts instrumenting W3WP app pool #------------------------------------ function start-instrumentation{ #set instrumentation on: & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsperfclrenv' /globaltraceon #restart the app pool and store the ID: kill-apppool start-apppool $procId = get-apppoolpid #start instrumenting: & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsperfcmd' /START:COVERAGE /OUTPUT:$coverageOutputPath /CS /USER:$appPoolIdentity & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsperfcmd' /ATTACH:$procId & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsperfcmd' /status } #-------------------- # Stops instrumenting #-------------------- function stop-instrumentation{ #stop instrumenting & reset app pool: & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsperfcmd' /DETACH kill-apppool & 'C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Performance Tools\vsperfcmd' /SHUTDOWN } #------------------------ # Exports coverage to XML #------------------------ function export-coverage{ #export the coverage file to XML: [System.Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft Visual Studio 10.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 #-------------- #set variables: $solutionFriendlyName = 'XYZ' $wakeUpServiceUrl = 'http://localhost/x.y.z/Service.svc' $appPoolName = 'ap_XYZ' $appPoolIdentity = 'domain\svc_user' $websiteBinDirectory = 'c:\websites\xyz\x.y.z.Services\bin' $coverageOutputPath = "Test.coverage" #instrument assemblies: # - instrument assembly so ALL namespaces are included in coverage instrument "x.y.z.Services.dll" # - instrument assembly so anything from the Ignore1 and Ignore2 namespaces are excluded from coverage instrument "x.y.z.Core.dll" 'x.y.z.Core.Ignore1.*' , 'x.y.z.Core.Ignore2.*' 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" & 'C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe' Build.proj /t:$msBuildTarget /p:ConfigurationName=$configurationName $realExitCode = $LASTEXITCODE write-host "Before stopping instrumentation, last exit code: $LASTEXITCODE, real exit code: $realExitCode" stop-instrumentation export-coverage export-coverage if ($deleteInstrumentedAssemblies -eq $true){ delete-intstrumentedassemblies } write-host "Before existing, last exit code: $LASTEXITCODE, real exit code: $realExitCode" exit $realExitCode #------------ # Script ends #------------