@@ -97,6 +97,11 @@ public async Task UpdateTestResults(int testRunId, Dictionary<string, TestResult
9797 await UploadTestResultFiles ( testRunId , testCaseTestResults , testResultsByParent , cancellationToken ) ;
9898 }
9999
100+ public async Task UpdateTestResults ( int testRunId , VstpTestRunComplete testRunComplete , CancellationToken cancellationToken )
101+ {
102+ await UploadTestResultFiles ( testRunId , null , testRunComplete . Attachments , cancellationToken ) ;
103+ }
104+
100105 public async Task < int [ ] > AddTestCases ( int testRunId , string [ ] testCaseNames , DateTime startedDate , string source , CancellationToken cancellationToken )
101106 {
102107 string requestBody = "[ " + string . Join ( ", " , testCaseNames . Select ( x =>
@@ -154,9 +159,33 @@ public async Task MarkTestRunCompleted(int testRunId, DateTime startedDate, Date
154159
155160 protected Dictionary < string , object > GetTestResultProperties ( ITestResult testResult )
156161 {
162+ // https://docs.microsoft.com/en-us/rest/api/azure/devops/test/results/list?view=azure-devops-rest-6.0#testcaseresult
163+ // outcome valid values = (Unspecified, None, Passed, Failed, Inconclusive, Timeout, Aborted, Blocked, NotExecuted, Warning, Error, NotApplicable, Paused, InProgress, NotImpacted)
164+ string testOutcome ;
165+ switch ( testResult . Outcome )
166+ {
167+ case TestOutcome . None :
168+ testOutcome = "None" ;
169+ break ;
170+ case TestOutcome . Passed :
171+ testOutcome = "Passed" ;
172+ break ;
173+ case TestOutcome . Failed :
174+ testOutcome = "Failed" ;
175+ break ;
176+ case TestOutcome . Skipped :
177+ testOutcome = "Inconclusive" ;
178+ break ;
179+ case TestOutcome . NotFound :
180+ testOutcome = "NotExecuted" ;
181+ break ;
182+ default :
183+ throw new ArgumentOutOfRangeException ( nameof ( testResult . Outcome ) , testResult . Outcome . ToString ( ) ) ;
184+ }
185+
157186 Dictionary < string , object > properties = new Dictionary < string , object >
158187 {
159- { "outcome" , testResult . Outcome . ToString ( ) } ,
188+ { "outcome" , testOutcome } ,
160189 { "computerName" , testResult . ComputerName } ,
161190 { "runBy" , new Dictionary < string , object > { { "displayName" , BuildRequestedFor } } }
162191 } ;
@@ -285,25 +314,39 @@ private async Task UploadTestResultFiles(int testRunId, Dictionary<string, TestR
285314
286315 foreach ( ITestResult testResult in testResultByParent . Select ( x => x ) )
287316 {
288- if ( testResult . Attachments . Count > 0 )
289- {
290- Console . WriteLine ( $ "Attaching files to test run { testRunId } and test result { parent . Id } ..." ) ;
291- }
317+ await UploadTestResultFiles ( testRunId , parent . Id , testResult . Attachments , cancellationToken ) ;
318+ }
319+ }
320+ }
292321
293- foreach ( AttachmentSet attachmentSet in testResult . Attachments )
294- {
295- if ( attachmentSet . Attachments . Count > 0 )
296- {
297- Console . WriteLine ( $ "Attaching files in set { attachmentSet . DisplayName } { attachmentSet . Uri } ...") ;
298- }
322+ private async Task UploadTestResultFiles ( int testRunId , int ? testResultId , ICollection < AttachmentSet > attachmentSets , CancellationToken cancellationToken )
323+ {
324+ if ( attachmentSets . Count > 0 )
325+ {
326+ string message = $ "Attaching files to test run { testRunId } ";
299327
300- foreach ( UriDataAttachment attachment in attachmentSet . Attachments )
301- {
302- Console . WriteLine ( $ "Attaching file { attachment . Description } { attachment . Uri . LocalPath } ...") ;
328+ if ( testResultId != null )
329+ {
330+ message += $ " and test result { testResultId } ";
331+ }
303332
304- await AttachFile ( testRunId , parent . Id , attachment . Uri . LocalPath , attachment . Description , cancellationToken ) ;
305- }
306- }
333+ message += "..." ;
334+
335+ Console . WriteLine ( message ) ;
336+ }
337+
338+ foreach ( AttachmentSet attachmentSet in attachmentSets )
339+ {
340+ if ( attachmentSet . Attachments . Count > 0 )
341+ {
342+ Console . WriteLine ( $ "Attaching files in set { attachmentSet . DisplayName } { attachmentSet . Uri } ...") ;
343+ }
344+
345+ foreach ( UriDataAttachment attachment in attachmentSet . Attachments )
346+ {
347+ Console . WriteLine ( $ "Attaching file { attachment . Description } { attachment . Uri . LocalPath } ...") ;
348+
349+ await AttachFile ( testRunId , testResultId , attachment . Uri . LocalPath , attachment . Description , cancellationToken ) ;
307350 }
308351 }
309352 }
@@ -314,29 +357,46 @@ private async Task AttachTextAsFile(int testRunId, int testResultId, string file
314357 await AttachFile ( testRunId , testResultId , contentAsBytes , fileName , comment , cancellationToken ) ;
315358 }
316359
317- private async Task AttachFile ( int testRunId , int testResultId , string filePath , string comment , CancellationToken cancellationToken )
360+ private async Task AttachFile ( int testRunId , int ? testResultId , string filePath , string comment , CancellationToken cancellationToken )
318361 {
319362 byte [ ] contentAsBytes = File . ReadAllBytes ( filePath ) ;
320363 string fileName = Path . GetFileName ( filePath ) ;
321364 await AttachFile ( testRunId , testResultId , contentAsBytes , fileName , comment , cancellationToken ) ;
322365 }
323366
324- private async Task AttachFile ( int testRunId , int testResultId , byte [ ] fileContents , string fileName , string comment , CancellationToken cancellationToken )
367+ private async Task AttachFile ( int testRunId , int ? testResultId , byte [ ] fileContents , string fileName , string comment , CancellationToken cancellationToken )
325368 {
326- // https://docs.microsoft.com/en-us/rest/api/azure/devops/test/attachments/create%20test%20result%20attachment
327- // https://docs.microsoft.com/en-us/azure/devops/integrate/previous-apis/test/attachments?view=tfs-2015#attach-a-file-to-a-test-result
328369 string contentAsBase64 = Convert . ToBase64String ( fileContents ) ;
329370
371+ string attachmentType = "GeneralAttachment" ;
372+
373+ if ( fileName . EndsWith ( ".coverage" , StringComparison . OrdinalIgnoreCase ) )
374+ {
375+ attachmentType = "CodeCoverage" ;
376+ }
377+
330378 Dictionary < string , object > props = new Dictionary < string , object >
331379 {
332380 { "stream" , contentAsBase64 } ,
333381 { "fileName" , fileName } ,
334382 { "comment" , comment } ,
335- { "attachmentType" , "GeneralAttachment" }
383+ { "attachmentType" , attachmentType }
336384 } ;
337385
338386 string requestBody = props . ToJson ( ) ;
339- await SendAsync ( new HttpMethod ( "POST" ) , $ "/{ testRunId } /results/{ testResultId } /attachments", requestBody , cancellationToken , "2.0-preview" ) . ConfigureAwait ( false ) ;
387+
388+ if ( testResultId == null )
389+ {
390+ // https://docs.microsoft.com/en-us/rest/api/azure/devops/test/attachments/create%20test%20run%20attachment
391+ // https://docs.microsoft.com/en-us/previous-versions/azure/devops/integrate/previous-apis/test/attachments?view=tfs-2015#attach-a-file-to-a-test-run
392+ await SendAsync ( new HttpMethod ( "POST" ) , $ "/{ testRunId } /attachments", requestBody , cancellationToken , "2.0-preview" ) . ConfigureAwait ( false ) ;
393+ }
394+ else
395+ {
396+ // https://docs.microsoft.com/en-us/rest/api/azure/devops/test/attachments/create%20test%20result%20attachment
397+ // https://docs.microsoft.com/en-us/azure/devops/integrate/previous-apis/test/attachments?view=tfs-2015#attach-a-file-to-a-test-result
398+ await SendAsync ( new HttpMethod ( "POST" ) , $ "/{ testRunId } /results/{ testResultId } /attachments", requestBody , cancellationToken , "2.0-preview" ) . ConfigureAwait ( false ) ;
399+ }
340400 }
341401 }
342402}
0 commit comments