Running Microsoft Test Manager Test Suites as part of a vNext Release pipeline - Part 2
In my last post I discussed how you could wire TCM tests into a Release Management vNext pipeline. The problem with the script I provided, as I noted, was that the deployment was triggered synchronously by the build i.e. the build/release process was:
- TFS Build
- Gets the source
- Compiled the code
- Run the unit tests
- Trigger the RM pipeline
- Wait while the RM pipeline completed
- RM then
- Deploys the code
- Runs the integration tests
- When RM completed the TFS build completes
This process raised a couple of problems
- You cannot associate the integration tests with the build as TCM only allow association with completed successful builds. When TCM finishes in this model the build is still in progress.
- You have to target only the first automated stage of the pipeline, else the build will be held as ‘in progress’ until all the release stages have complete, which may be days if there are manual approvals involved
The script InitiateReleaseFromBuild
These problems can all be fixed by altering the PowerShell that triggers the RM pipeline so that it does not wait for the deployment to complete, so the TFS build completes as soon as possible.
This is done by passing in an extra parameter which is set in TFS build
1param(
2 \[string\]$rmserver = $Args\[0\],
3 \[string\]$port = $Args\[1\],
4 \[string\]$teamProject = $Args\[2\],
5 \[string\]$targetStageName = $Args\[3\],
6 \[string\]$waitForCompletion = $Args\[4\]
7)
8
9cls
10$teamFoundationServerUrl = $env:TF\_BUILD\_COLLECTIONURI
11$buildDefinition = $env:TF\_BUILD\_BUILDDEFINITIONNAME
12$buildNumber = $env:TF\_BUILD\_BUILDNUMBER
13
14
15
16
17"Executing with the following parameters:\`n"
18" RMserver Name: $rmserver"
19" Port number: $port"
20" Team Foundation Server URL: $teamFoundationServerUrl"
21" Team Project: $teamProject"
22" Build Definition: $buildDefinition"
23" Build Number: $buildNumber"
24" Target Stage Name: $targetStageName\`n"
25" Wait for RM completion: $waitForCompletion\`n"
26
27
28
29$wait = \[System.Convert\]::ToBoolean($waitForCompletion)
30$exitCode = 0
31
32
33
34trap
35{
36 $e = $error\[0\].Exception
37 $e.Message
38 $e.StackTrace
39 if ($exitCode -eq 0) { $exitCode = 1 }
40}
41
42
43
44$scriptName = $MyInvocation.MyCommand.Name
45$scriptPath = Split-Path -Parent (Get-Variable MyInvocation -Scope Script).Value.MyCommand.Path
46
47
48
49Push-Location $scriptPath
50
51
52
53$server = \[System.Uri\]::EscapeDataString($teamFoundationServerUrl)
54$project = \[System.Uri\]::EscapeDataString($teamProject)
55$definition = \[System.Uri\]::EscapeDataString($buildDefinition)
56$build = \[System.Uri\]::EscapeDataString($buildNumber)
57$targetStage = \[System.Uri\]::EscapeDataString($targetStageName)
58
59
60
61$serverName = $rmserver + ":" + $port
62$orchestratorService = "[http://$serverName/account/releaseManagementService/\_apis/releaseManagement/OrchestratorService"](http://$serverName/account/releaseManagementService/_apis/releaseManagement/OrchestratorService")
63
64
65
66$status = @{
67 "2" = "InProgress";
68 "3" = "Released";
69 "4" = "Stopped";
70 "5" = "Rejected";
71 "6" = "Abandoned";
72}
73
74
75
76$uri = "$orchestratorService/InitiateReleaseFromBuild?teamFoundationServerUrl=$server&teamProject=$project&buildDefinition=$definition&buildNumber=$build&targetStageName=$targetStage"
77"Executing the following API call:\`n\`n$uri"
78
79
80
81$wc = New-Object System.Net.WebClient
82$wc.UseDefaultCredentials = $true
83\# rmuser should be part rm users list and he should have permission to trigger the release.
84
85
86
87#$wc.Credentials = new-object System.Net.NetworkCredential("rmuser", "rmuserpassword", "rmuserdomain")
88
89
90
91try
92{
93 $releaseId = $wc.DownloadString($uri)
94
95
96
97 $url = "$orchestratorService/ReleaseStatus?releaseId=$releaseId"
98
99
100
101 $releaseStatus = $wc.DownloadString($url)
102
103
104
105
106 if ($wait -eq $true)
107 {
108 Write-Host -NoNewline "\`nReleasing ..."
109
110
111
112 while($status\[$releaseStatus\] -eq "InProgress")
113 {
114 Start-Sleep -s 5
115 $releaseStatus = $wc.DownloadString($url)
116 Write-Host -NoNewline "."
117 }
118
119
120
121 " done.\`n\`nRelease completed with {0} status." -f $status\[$releaseStatus\]
122 } else {
123
124
125
126 Write-Host -NoNewline "\`nTriggering Release and exiting"
127 }
128
129
130
131}
132catch \[System.Exception\]
133{
134 if ($exitCode -eq 0) { $exitCode = 1 }
135 Write-Host "\`n$\_\`n" -ForegroundColor Red
136}
137
138
139
140if ($exitCode -eq 0)
141{
142 if ($wait -eq $true)
143 {
144 if ($releaseStatus -eq 3)
145 {
146 "\`nThe script completed successfully. Product deployed without error\`n"
147 } else {
148 Write-Host "\`nThe script completed successfully. Product failed to deploy\`n" -ForegroundColor Red
149 $exitCode = -1 # reset the code to show the error
150 }
151 } else {
152 "\`nThe script completed successfully. Product deploying\`n"
153 }
154}
155else
156{
157 $err = "Exiting with error: " + $exitCode + "\`n"
158 Write-Host $err -ForegroundColor Red
159}
160
161
162
163Pop-Location
164
165
166
167exit $exitCode
The Script TcmExecWrapper
A change is also required in the wrapper script I use to trigger the TCM test run. We need to check the exit code from the inner TCM PowerShell script and update the TFS build quality appropriately.
To this I use the new REST API in TFS 2015 as this is far easier than using the older .NET client API. No DLLs to distribute.
It is worth noticing that
- I pass the credentials into the script from RM that are used to talk to the TFS server. This is because I am running my tests in a network isolated TFS Lab Environment, this means I am in the wrong domain to see the TFS server without providing login details. If you are not working cross domain you could just use Default Credentials.
- RM only passes the BuildNumber into the script e.g. MyBuild_1.2.3.4, but the REST API need the build id to set the quality. Hence the need for function Get-BuildDetailsByNumber to get the id from the name
1\# Output execution parameters.
2$VerbosePreference ='Continue' # equiv to -verbose
3function Get-BuildDetailsByNumber
4{
5 param
6 (
7 $tfsUri ,
8 $buildNumber,
9 $username,
10 $password
11 )
12 $uri = "$($tfsUri)/\_apis/build/builds?api-version=2.0&buildnumber=$buildNumber"
13 $wc = New-Object System.Net.WebClient
14 #$wc.UseDefaultCredentials = $true
15 $wc.Credentials = new-object System.Net.NetworkCredential($username, $password)
16
17 write-verbose "Getting ID of $buildNumber from $tfsUri "
18 $jsondata = $wc.DownloadString($uri) | ConvertFrom-Json
19 $jsondata.value\[0\]
20
21}
22function Set-BuildQuality
23{
24 param
25 (
26 $tfsUri ,
27 $buildID,
28 $quality,
29 $username,
30 $password
31 )
32 $uri = "$($tfsUri)/\_apis/build/builds/$($buildID)?api-version=1.0"
33 $data = @{quality = $quality} | ConvertTo-Json
34 $wc = New-Object System.Net.WebClient
35 $wc.Headers\["Content-Type"\] = "application/json"
36 #$wc.UseDefaultCredentials = $true
37 $wc.Credentials = new-object System.Net.NetworkCredential($username, $password)
38
39 write-verbose "Setting BuildID $buildID to quality $quality via $tfsUri "
40 $wc.UploadString($uri,"PATCH", $data)
41
42}
43$folder = Split-Path -Parent $MyInvocation.MyCommand.Definition
44write-verbose "Running $folderTcmExecWithLogin.ps1"
45& "$folderTcmExecWithLogin.ps1" -Collection $Collection -Teamproject $Teamproject -PlanId $PlanId -SuiteId $SuiteId -ConfigId $ConfigId -BuildDirectory $PackageLocation -TestEnvironment $TestEnvironment -LoginCreds "$TestUserUid,$TestUserPwd" -SettingsName $SettingsName -BuildNumber $BuildNumber -BuildDefinition $BuildDefinition
46write-verbose "Got the exit code from the TCM run of $LASTEXITCODE"
47$url = "$Collection/$Teamproject"
48$jsondata = Get-BuildDetailsByNumber -tfsUri $url -buildNumber $BuildNumber -username $TestUserUid -password $TestUserPwd
49$buildId = $jsondata.id
50write-verbose "The build ID is $buildId"
51$newquality = "Test Passed"
52if ($LASTEXITCODE -gt 0 )
53{
54 $newquality = "Test Failed"
55}
56
57write-verbose "The build quality is $newquality"
58Set-BuildQuality -tfsUri $url -buildID $buildId -quality $newquality -username $TestUserUid -password $TestUserPwd
Note: TcmExecWithLogin.ps1 is the same as in the In my last post
Summary
So with these changes the process is now
- TFS Build
- Gets the source
- Compiled the code
- Run the unit tests
- Trigger the RM pipeline
- Build ends
- RM then
- Deploys the code
- Runs the integration tests
- When the test complete we set the TFS build quality
This means we can associate both unit and integration tests with a build and target our release at any stage in the pipeline, it pausing at the points manual approval is required without blocking the initiating build.