@@ -13,9 +13,26 @@ import FeatureFlags, { FeatureFlag } from './services/FeatureFlags';
1313
1414class TaskParameters {
1515
16+ // dependencies
1617 tph : TaskParameterHelper
18+
19+ // cached parameters
20+ accessToken ?: string ;
21+ collectionUri ?: string ;
22+ projectName ?: string ;
23+
24+ buildId ?: string ;
25+ releaseUri ?: string ;
26+ releaseEnvironmentUri ?: string ;
27+
28+ testFiles : string [ ] = [ ] ;
29+
1730 constructor ( tph : TaskParameterHelper ) {
1831 this . tph = tph ;
32+ const { buildId, releaseUri, releaseEnvironmentUri } = this . #getPipelineEnvironment( ) ;
33+ this . buildId = buildId ;
34+ this . releaseUri = releaseUri ;
35+ this . releaseEnvironmentUri = releaseEnvironmentUri ;
1936 }
2037
2138 static getInstance ( ) : TaskParameters {
@@ -27,154 +44,194 @@ class TaskParameters {
2744 /* Fetch the parameters used to obtain the working details for the ADO Test Plan */
2845 getTestContextParameters ( ) : TestResultContextParameters {
2946 tl . debug ( "reading TestContextParameters from task inputs." ) ;
47+ this . tph . recordStage ( "getTestContextParameters" ) ;
3048
31- const accessToken = tl . getInput ( "accessToken" , false ) ?? tl . getVariable ( "SYSTEM_ACCESSTOKEN" ) ;
32- const collectionUri = tl . getInput ( "collectionUri" , false ) ?? tl . getVariable ( "SYSTEM_COLLECTIONURI" ) ;
33- const projectName = tl . getInput ( "projectName" , false ) ?? tl . getVariable ( "SYSTEM_TEAMPROJECT" ) ;
34-
35- var parameters = new TestResultContextParameters (
36- ( collectionUri as string ) ,
37- ( projectName as string ) ,
38- ( accessToken as string ) ) ;
49+ this . #ensureCredentialsAreSet( ) ;
50+ var parameters = new TestResultContextParameters ( this . collectionUri ! , this . projectName ! , this . accessToken ! ) ;
3951
40- parameters . testPlan = tl . getInput ( "testPlan" , false ) ;
41- parameters . testConfigFilter = tl . getInput ( "testConfigFilter" , false ) ;
42- tl . getDelimitedInput ( "testConfigAliases" , "," , false ) . forEach ( ( alias : string ) => {
52+ parameters . testPlan = this . tph . getInput ( "testPlan" , false , { recordNonDefault : true } ) ! ;
53+ parameters . testConfigFilter = this . tph . getInput ( "testConfigFilter" , false , { recordNonDefault : true } ) ;
54+ this . tph . getDelimitedInput ( "testConfigAliases" , { recordNonDefault : true } ) . forEach ( ( alias : string ) => {
4355 let parts = alias . split ( "=" ) ;
4456 if ( parts . length > 1 ) {
4557 parameters . testConfigAliases . push ( new configAlias ( parts [ 0 ] , parts [ 1 ] ) ) ;
4658 }
4759 } ) ;
4860
61+ this . tph . recordStage ( "createContext" ) ;
4962 return parameters ;
5063 }
5164
5265 /* Fetch the parameters used to parse through automated test results */
5366 getFrameworkParameters ( ) : TestFrameworkParameters {
5467 tl . debug ( "reading TestFrameworkParameters from task inputs." ) ;
68+ this . tph . recordStage ( "getFrameworkParameters" ) ;
5569
56- let testResultFormat = tl . getInput ( "testResultFormat" , true ) ;
57- let failTaskOnMissingResultsFile = getBoolInput ( "failTaskOnMissingResultsFile" , /*default*/ true ) ;
58- let failTaskOnMissingTests = getBoolInput ( "failTaskOnMissingTests" , /*default*/ false ) ;
59- let testResultFiles = getTestFiles ( failTaskOnMissingResultsFile ) ;
60-
61- return new TestFrameworkParameters ( testResultFiles , testResultFormat ! . toLowerCase ( ) , failTaskOnMissingResultsFile , failTaskOnMissingTests ) ;
62- }
70+ let testResultFormat = this . tph . getInput ( "testResultFormat" , true , { recordValue : true } ) ;
71+ let failTaskOnMissingResultsFile = this . tph . getBoolInput ( "failTaskOnMissingResultsFile" , /*default*/ true , { recordValue : true , recordNonDefault : true } ) ;
72+ let failTaskOnMissingTests = this . tph . getBoolInput ( "failTaskOnMissingTests" , /*default*/ false , { recordValue : true , recordNonDefault : true } ) ;
73+ this . testFiles = this . #getTestFiles( failTaskOnMissingResultsFile ) ;
74+
75+ const parameters = new TestFrameworkParameters ( this . testFiles , testResultFormat ! . toLowerCase ( ) , failTaskOnMissingResultsFile , failTaskOnMissingTests ) ;
76+ this . tph . recordStage ( "readFrameworkResults" ) ;
77+ return parameters ;
78+ }
6379
6480 /* Fetch the parameters used to process test results and match them to test cases */
6581 getProcessorParameters ( ) : TestResultProcessorParameters {
6682 tl . debug ( "reading TestResultProcessorParameters from task inputs." ) ;
83+ this . tph . recordStage ( "getProcessorParameters" ) ;
6784
68- let matchingStrategy = tl . getInput ( "testCaseMatchStrategy" , false ) ?? "Auto" ;
85+ let matchingStrategy = this . tph . getInputOrFallback ( "testCaseMatchStrategy" , ( ) => "Auto" , { recordValue : true , recordNonDefault : true , dontRecordDefault : true } ) ! ;
6986 var parameters = new TestResultProcessorParameters ( matchingStrategy ) ;
7087
7188 // optional parameters
72- parameters . testConfigFilter = tl . getInput ( "testConfigFilter" , false ) ;
73- parameters . testCaseProperty = tl . getInput ( "testCaseProperty" , false ) ?? "TestCase" ;
74- parameters . testCaseRegEx = tl . getInput ( "testCaseRegex" , false ) ?? "(\\d+)" ;
75- parameters . testConfigProperty = tl . getInput ( "testConfigProperty" , false ) ?? "Config" ;
89+ parameters . testConfigFilter = this . tph . getInput ( "testConfigFilter" , false , { recordNonDefault : true } ) ;
90+ parameters . testCaseProperty = this . tph . getInputOrFallback ( "testCaseProperty" , ( ) => "TestCase" , { recordValue : true , recordNonDefault : true , dontRecordDefault : true } ) ;
91+ parameters . testCaseRegEx = this . tph . getInputOrFallback ( "testCaseRegex" , ( ) => "(\\d+)" , { recordValue : true , recordNonDefault : true , dontRecordDefault : true } ) ;
92+ parameters . testConfigProperty = this . tph . getInputOrFallback ( "testConfigProperty" , ( ) => "Config" , { recordValue : true , recordNonDefault : true , dontRecordDefault : true } ) ;
7693
94+ this . tph . recordStage ( "processFrameworkResults" ) ;
7795 return parameters ;
7896 }
7997
8098 /* Fetch the parameters used to publish test results to ADO Test Plan */
8199 getPublisherParameters ( ) : TestRunPublisherParameters {
82100 tl . debug ( "reading TestRunPublisherParameters from task inputs." ) ;
83-
84- const accessToken = tl . getInput ( "accessToken" , false ) ?? tl . getVariable ( "SYSTEM_ACCESSTOKEN" ) ;
85- const buildId = tl . getVariable ( "BUILD_BUILDID" ) ! ; // available in build and release pipelines
86- const releaseUri = tl . getVariable ( "RELEASE_RELEASEURI" ) ; // only in release pipelines
87- const releaseEnvironmentUri = tl . getVariable ( "RELEASE_ENVIRONMENTURI" ) ; // only in release pipelines
88- const collectionUri = tl . getInput ( "collectionUri" , false ) ?? tl . getVariable ( "SYSTEM_COLLECTIONURI" ) ! ;
89- const dryRun = tl . getBoolInput ( "dryRun" , false ) ;
90- const testRunTitle = tl . getInput ( "testRunTitle" , false ) ?? "PublishTestPlanResult" ;
91- let verifyFiles = getBoolInput ( "failTaskOnMissingResultsFile" , /*default*/ true ) ;
92- let failTaskOnUnmatchedTestCases = getBoolInput ( "failTaskOnUnmatchedTestCases" , /*default*/ true ) ;
93- const testFiles = getTestFiles ( verifyFiles ) . filter ( file => file . indexOf ( '**' ) == - 1 ) ;
101+ this . tph . recordStage ( "getPublisherParameters" ) ;
102+
103+ this . #ensureCredentialsAreSet( ) ;
104+ const dryRun = this . tph . getBoolInput ( "dryRun" , false , { recordValue : true , dontRecordDefault : true } ) ;
105+ const testRunTitle = this . tph . getInputOrFallback ( "testRunTitle" , ( ) => "PublishTestPlanResult" , { recordNonDefault : true } ) ;
106+ let failTaskOnUnmatchedTestCases = this . tph . getBoolInput ( "failTaskOnUnmatchedTestCases" , /*default*/ true , { recordValue : true , dontRecordDefault : true } ) ;
107+ const testFiles = this . testFiles . filter ( file => file . indexOf ( '**' ) == - 1 ) ;
94108 let result = new TestRunPublisherParameters (
95- collectionUri ,
96- accessToken as string ,
109+ this . collectionUri ! ,
110+ this . accessToken ! ,
97111 dryRun ,
98112 testRunTitle ,
99- buildId ,
113+ this . buildId ! ,
100114 testFiles ,
101115 failTaskOnUnmatchedTestCases
102116 ) ;
103- result . releaseUri = releaseUri ;
104- result . releaseEnvironmentUri = releaseEnvironmentUri ;
117+ result . releaseUri = this . releaseUri ;
118+ result . releaseEnvironmentUri = this . releaseEnvironmentUri ;
119+
120+ this . tph . recordStage ( "publishTestRunResults" ) ;
105121 return result ;
106122 }
107123
108124 /* Fetch the parameters used to filter test results and finalize task outcome */
109125 getStatusFilterParameters ( ) : StatusFilterParameters {
110126 tl . debug ( "reading StatusFilterParameters from task inputs." ) ;
127+ this . tph . recordStage ( "getStatusFilterParameters" ) ;
111128
112129 var parameters = new StatusFilterParameters ( ) ;
113- parameters . failTaskOnFailedTests = getBoolInput ( "failTaskOnFailedTests" , /*default*/ false ) ;
114- parameters . failTaskOnSkippedTests = getBoolInput ( "failTaskOnSkippedTests" , /*default*/ false ) ;
130+ parameters . failTaskOnFailedTests = this . tph . getBoolInput ( "failTaskOnFailedTests" , /*default*/ false , { recordValue : true , dontRecordDefault : true } ) ;
131+ parameters . failTaskOnSkippedTests = this . tph . getBoolInput ( "failTaskOnSkippedTests" , /*default*/ false , { recordValue : true , dontRecordDefault : true } ) ;
115132
133+ this . tph . recordStage ( "finalizeResults" ) ;
116134 return parameters ;
117135 }
118136
119137 /* Fetch the telemetry payload for this task execution */
120138 getTelemetryParameters ( err ?: any ) : TelemetryPublisherParameters {
121139 const hasError = err !== undefined && err !== null ;
122- const withErrorOrWithoutError = hasError ? "with error" : "without error " ;
140+ const withErrorOrWithoutError = hasError ? "( error condition) " : "" ;
123141 tl . debug ( `reading TelemetryPublisherParameters from task inputs ${ withErrorOrWithoutError } .` ) ;
142+ // don't record stage so that we can publish which stage we last completed
124143
125144 const result = new TelemetryPublisherParameters ( ) ;
126145 result . displayTelemetryPayload = FeatureFlags . isFeatureEnabled ( FeatureFlag . DisplayTelemetry ) ;
127146 result . displayTelemetryErrors = FeatureFlags . isFeatureEnabled ( FeatureFlag . DisplayTelemetryErrors ) ;
147+ result . publishTelemetry = FeatureFlags . isFeatureEnabled ( FeatureFlag . PublishTelemetry ) ;
128148
129149 result . payload = this . tph . getPayload ( err ) ; // todo: specify privacy level
130150 result . payload [ "flags" ] = FeatureFlags . getFlags ( ) ;
131151 return result ;
132152 }
133- }
134153
135- export default TaskParameters . getInstance ( ) ;
136- export { TaskParameters } ;
137-
138- function getTestFiles ( verifyFiles : boolean ) : string [ ] {
139-
140- let testResultFolder = tl . getInput ( "testResultDirectory" , false ) ;
141- if ( testResultFolder == undefined ) {
142- // System.DefaultWorkingDirectory:
143- // - build pipelines: "C:\agent\work\1\s" equivalent to "$(Build.SourcesDirectory)"
144- // - release pipelines: "C:\agent\work\r1\a" equivalent to "$(System.ArtifactsDirectory)"
145- testResultFolder = tl . getVariable ( "SYSTEM_DEFAULTWORKINGDIRECTORY" ) as string ;
146-
147- tl . debug ( `testResultDirectory was not specified. Using default working directory: ${ testResultFolder } ` ) ;
154+ #ensureCredentialsAreSet( ) {
155+ if ( ! this . accessToken ) {
156+ this . accessToken = this . tph . getInputOrFallback ( "accessToken" , ( ) => tl . getVariable ( "SYSTEM_ACCESSTOKEN" ) , { recordNonDefault : true } ) ;
157+ this . projectName = this . tph . getInputOrFallback ( "projectName" , ( ) => tl . getVariable ( "SYSTEM_TEAMPROJECT" ) , { recordNonDefault : true , anonymize : true } ) ;
158+ this . collectionUri = this . tph . getInputOrFallback ( "collectionUri" , ( ) => tl . getVariable ( "SYSTEM_COLLECTIONURI" ) , { recordNonDefault : true , anonymize : true } ) ;
159+
160+ let serverType = ( this . collectionUri && ( this . collectionUri . startsWith ( "https://dev.azure.com/" ) || this . collectionUri . includes ( ".visualstudio.com" ) ) ) ?
161+ "Hosted" : "OnPremises" ;
162+ this . tph . payloadBuilder . add ( "serverType" , serverType ) ;
163+ }
148164 }
149165
150- let testResultFiles = tl . getDelimitedInput ( "testResultFiles" , "," , true )
151- . map ( file => {
152- // merge relative paths with the testresult folder
153- if ( ! path . isAbsolute ( file ) ) {
154- tl . debug ( `joining relative path '${ file } ' with testResultDirectory '${ testResultFolder } '` ) ;
155- return path . join ( testResultFolder , file ) ;
156- }
157- return file ;
158- } )
159- . filter ( file => {
160- // if it's not a wildcard, verify that the file exists
161- if ( file . indexOf ( '**' ) == - 1 ) {
162- // either filter out missing files, or fail the task based on user-preference
163- if ( verifyFiles ) {
164- // fail if the file does not exist
165- tl . checkPath ( file , "testResultFile(s)" ) ;
166- } else {
167- // task supports missing files, so filter out missing files
168- return tl . exist ( file ) ;
166+ #getTestFiles( verifyFiles : boolean ) : string [ ] {
167+ var wildCardUsed : boolean | undefined = undefined ;
168+
169+ // Resolve the user specified testResultDirectory.
170+ // If not specified, default to SYSTEM_DEFAULTWORKINGDIRECTORY.
171+ // - For Build Pipelines: "C:\agent\work\1\s" equivalent to "$(Build.SourcesDirectory)"
172+ // - For Release Pipelines: "C:\agent\work\r1\a" equivalent to "$(System.ArtifactsDirectory)"
173+ let testResultFolder = this . tph . getInputOrFallback ( "testResultDirectory" , ( ) => tl . getVariable ( "SYSTEM_DEFAULTWORKINGDIRECTORY" ) ! , { recordNonDefault : true } ) ;
174+
175+ let testResultFiles = tl . getDelimitedInput ( "testResultFiles" , "," , true )
176+ . map ( file => {
177+ // merge relative paths with the testresult folder
178+ if ( ! path . isAbsolute ( file ) ) {
179+ tl . debug ( `joining relative path '${ file } ' with testResultDirectory '${ testResultFolder } '` ) ;
180+ return path . join ( testResultFolder , file ) ;
169181 }
170- }
171- return true ;
172- } ) ;
182+ return file ;
183+ } )
184+ . filter ( file => {
185+ // if it's not a wildcard, verify that the file exists
186+ if ( file . indexOf ( '**' ) == - 1 ) {
187+ // either filter out missing files, or fail the task based on user-preference
188+ if ( verifyFiles ) {
189+ // fail if the file does not exist
190+ tl . checkPath ( file , "testResultFile(s)" ) ;
191+ } else {
192+ // task supports missing files, so filter out missing files
193+ return tl . exist ( file ) ;
194+ }
195+ }
196+ else {
197+ wildCardUsed = true ;
198+ }
199+ return true ;
200+ } ) ;
201+
202+ // update telemetry
203+ if ( wildCardUsed ) {
204+ this . tph . payloadBuilder . add ( "testResultFilesWildcard" , true ) ;
205+ }
206+ if ( testResultFiles . length > 0 ) {
207+ this . tph . payloadBuilder . add ( "numTestFiles" , testResultFiles . length ) ;
208+ }
209+
210+ return testResultFiles ;
211+ }
173212
174- return testResultFiles ;
213+ #getPipelineEnvironment( ) : { buildId : string , releaseUri ?: string , releaseEnvironmentUri ?: string } {
214+
215+ // determine whether we're running in a build or release pipeline
216+ const buildId = tl . getVariable ( "BUILD_BUILDID" ) ! ; // available in build and release pipelines
217+ const releaseUri = tl . getVariable ( "RELEASE_RELEASEURI" ) ; // only in release pipelines
218+ const releaseEnvironmentUri = tl . getVariable ( "RELEASE_ENVIRONMENTURI" ) ; // only in release pipelines
219+
220+ // detect running in a build or a release
221+ let hostType = ( releaseUri && releaseEnvironmentUri ) ? "release" : "build" ;
222+ this . tph . payloadBuilder . add ( "hostType" , hostType ) ;
223+
224+ // detect if we're using a hosted or self-hosted agent
225+ let agentType = tl . getVariable ( "Agent.CloudId" ) ? "Hosted" : "OnPremises" ;
226+ this . tph . payloadBuilder . add ( "agentType" , agentType ) ;
227+
228+ // collect the agent version
229+ let agentVersion = tl . getVariable ( "Agent.Version" ) || '' ;
230+ this . tph . payloadBuilder . add ( "agentVersion" , agentVersion ) ;
231+
232+ return { buildId, releaseUri, releaseEnvironmentUri } ;
233+ }
175234}
176235
177- function getBoolInput ( name : string , defaultValue : boolean ) : boolean {
178- let input = tl . getInput ( name , false ) ;
179- return input ? tl . getBoolInput ( name , false ) : defaultValue ;
180- }
236+ export default TaskParameters . getInstance ( ) ;
237+ export { TaskParameters } ;
0 commit comments