Skip to content

Commit b17b8cd

Browse files
authored
Create output variables for TestRun (#121)
* revert chai version 6.2.0 is incompatible with ES Module * create output variables * Add regression test for TestRunId * use runtime expression for joboutput variable * try macro syntax * file path correction for xUnitExample * additional logging / fix for expectedResults * assertion in validation script
1 parent e8fb1e0 commit b17b8cd

5 files changed

Lines changed: 177 additions & 19 deletions

File tree

PublishTestPlanResultsV1/package-lock.json

Lines changed: 93 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

PublishTestPlanResultsV1/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@types/node": "^20.19.22",
2222
"@types/q": "^1.5.7",
2323
"@types/sinon": "^17.0.4",
24-
"chai": "^6.2.0",
24+
"chai": "^4.5.0",
2525
"sinon": "^21.0.0",
2626
"sync-request": "^6.1.0"
2727
}

PublishTestPlanResultsV1/publishing/TestRunPublisher.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as Contracts from "azure-devops-node-api/interfaces/TestInterfaces";
2+
import * as tl from 'azure-pipelines-task-lib/task';
23
import { TestAttachment, TestFrameworkResult } from "../framework/TestFrameworkResult";
34
import { TestRunPublisherParameters } from "./TestRunPublisherParameters";
45
import { TestResultProcessorResult } from "../processing/TestResultProcessor";
@@ -99,6 +100,8 @@ export class TestRunPublisher {
99100
var finalRun = await this.ado.updateTestRun(projectId, testRun);
100101

101102
this.logger.info(`Published Test Run: ${testRun.webAccessUrl}`)
103+
tl.setVariable("TestRunId", finalRun.id!.toString(), /*secret*/false, /*output*/true);
104+
tl.setVariable("TestRunUrl", finalRun.webAccessUrl!, /*secret*/false, /*output*/true);
102105

103106
return finalRun;
104107
} else {

PublishTestPlanResultsV1/test/TestRunPublisher.specs.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as Contracts from 'azure-devops-node-api/interfaces/TestInterfaces';
22
import { expect } from "chai";
33
import path from "path";
44
import sinon from "sinon";
5+
import * as tl from "azure-pipelines-task-lib/task";
56
import { TestResultProcessorResult } from "../processing/TestResultProcessor";
67
import { TestRunPublisher } from "../publishing/TestRunPublisher";
78
import { TestRunPublisherParameters } from "../publishing/TestRunPublisherParameters";
@@ -15,11 +16,15 @@ context("TestRunPublisher", () => {
1516
var ado : sinon.SinonStubbedInstance<AdoWrapper>;
1617
var testData : TestResultProcessorResult;
1718
var subject : TestRunPublisher;
19+
var tlStub : sinon.SinonStubbedInstance<typeof tl>;
1820

1921
beforeEach(() => {
2022
logger = new NullLogger();
2123
ado = sinon.createStubInstance(AdoWrapper);
2224

25+
// mock task library
26+
tlStub = sinon.stub(tl);
27+
2328
testData = new TestResultProcessorResult("project1", <Contracts.TestPlan>{ id: 1});
2429

2530
subject = new TestRunPublisher(ado, logger);
@@ -251,6 +256,20 @@ context("TestRunPublisher", () => {
251256
await testUtil.shouldThrowAsync(async () => { return subject.publishTestRun(testData)}, "Couldn't create a TestRun for this TestPlan because the test results could not be correlated to any known TestCases.")
252257
});
253258

259+
it("Should set output variables when test run is published", async () => {
260+
// arrange
261+
testData.matches.set(/*testpoint*/ 1, testUtil.newTestFrameworkResult());
262+
setupTestRun( /*runid*/ 400);
263+
setupTestCaseResults( [1] );
264+
265+
// act
266+
var result = await subject.publishTestRun(testData);
267+
268+
// assert
269+
expect(tlStub.setVariable.calledWith("TestRunId", "400", false, true)).eq(true);
270+
expect(tlStub.setVariable.calledWith("TestRunUrl", result?.webAccessUrl, false, true)).eq(true);
271+
})
272+
254273
function setupTestRun(runId: number) {
255274
ado.createTestRun.callsFake( (prjId, plnId, points) => {
256275

@@ -260,6 +279,11 @@ context("TestRunPublisher", () => {
260279

261280
return Promise.resolve(testRun);
262281
})
282+
283+
ado.updateTestRun.callsFake( (prjId, tr: Contracts.TestRun) => {
284+
tr.webAccessUrl = `https://dev.azure.com/fake/${tr.id}`;
285+
return Promise.resolve(tr);
286+
});
263287
}
264288
function setupTestCaseResults(points: number[]) {
265289
ado.getTestResults.callsFake( (prjId, runId) => {

devops/pipelines/marketplace-extension/regression-test.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ parameters:
1717
- name: JUnitExample-Maven
1818
format: junit
1919
testResultsFile: '$(Build.SourcesDirectory)/examples/java/JUnitExample-maven/TestResults.xml'
20+
- name: xUnitExample
21+
format: xunit
22+
testResultsFile: '$(Build.SourcesDirectory)/examples/dotnet/xUnitExample/TestResults/TestResults.xml'
23+
expectedResults:
24+
- outcome: Passed
25+
count: 7
26+
- outcome: Failed
27+
count: 1
28+
- outcome: NotExecuted
29+
count: 1
2030
- name: FailOnFailedTests
2131
format: nunit
2232
testResultsFile: '$(Build.SourcesDirectory)/examples/dotnet/NUnitExample/TestResults/TestResults.xml'
@@ -81,3 +91,49 @@ steps:
8191
-failTaskOnSkippedTests '${{ iif( ne(test.failTaskOnSkippedTests,''), test.failTaskOnSkippedTests, 'false' ) }}'
8292
-DryRun ''
8393
94+
- ${{ if ne(test.expectedResults, '') }}:
95+
- task: PowerShell@2
96+
displayName: 'Validate results for ${{ test.name }}'
97+
inputs:
98+
targetType: 'inline'
99+
script: |
100+
$expected = $env:expected | ConvertFrom-Json
101+
$testRun = $env:testrun
102+
$testRunUrl = $env:testrunurl
103+
104+
if (-not $testRun) {
105+
Write-Error "Test Run ID is not available. Cannot validate results."
106+
exit 0
107+
}
108+
if ( -not $testRunUrl) {
109+
Write-Error "Test Run URL is not available. Cannot validate results."
110+
exit 0
111+
}
112+
113+
Write-Host "Validating Test Run ID: $testRun"
114+
Write-Host "Test Run URL: $testRunUrl"
115+
116+
$auth64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$(System.AccessToken)"))
117+
$headers = @{ Authorization = "Basic $auth64" }
118+
$uri = "$(System.CollectionUri)/Test%20Plan%20Extension/_apis/test/runs/$($testRun)?api-version=7.1"
119+
120+
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
121+
122+
foreach ($expect in $expected) {
123+
$outcome = $expect.outcome
124+
$count = $expect.count
125+
126+
$actualCount = ($response.runStatistics | Where-Object { $_.outcome -eq $outcome }).count
127+
128+
if ($actualCount -ne $count) {
129+
Write-Error "Validation failed for outcome '$outcome'. Expected: $count, Actual: $actualCount"
130+
}
131+
else {
132+
Write-Host "Validation succeeded for outcome '$outcome'. Count: $count"
133+
}
134+
}
135+
136+
env:
137+
expected: ${{ convertToJson(test.expectedResults) }}
138+
testrun: $(${{ replace(lower(test.name), '-','_') }}.TestRunId)
139+
testrunurl: $(${{ replace(lower(test.name), '-','_') }}.TestRunUrl)

0 commit comments

Comments
 (0)