Skip to content

Commit b67d776

Browse files
matt-richardsonbradwilson
authored andcommitted
Defer reporting until TestComplete
1 parent ecace34 commit b67d776

File tree

3 files changed

+98
-8
lines changed

3 files changed

+98
-8
lines changed

Versions.props

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22

33
<PropertyGroup>
4-
<ILRepackVersion>2.0.36</ILRepackVersion>
4+
<ILRepackVersion>2.0.37</ILRepackVersion>
55
<MicrosoftNetCoreAppRefVersion>6.0.32</MicrosoftNetCoreAppRefVersion>
66
<MicrosoftNetFrameworkReferenceAssembliesVersion>1.0.3</MicrosoftNetFrameworkReferenceAssembliesVersion>
77
<MicrosoftNetTestSdkVersion>17.12.0</MicrosoftNetTestSdkVersion>
@@ -10,9 +10,9 @@
1010
<NerdbankGitVersioningVersion>3.7.115</NerdbankGitVersioningVersion>
1111
<NSubstituteVersion>5.3.0</NSubstituteVersion>
1212
<TunnelVisionLabsReferenceAssemblyAnnotatorVersion>1.0.0-alpha.160</TunnelVisionLabsReferenceAssemblyAnnotatorVersion>
13-
<XunitAnalyzersVersion>1.20.0-pre.4</XunitAnalyzersVersion>
13+
<XunitAnalyzersVersion>1.20.0-pre.9</XunitAnalyzersVersion>
1414
<XunitV2Version>2.9.3</XunitV2Version>
15-
<XunitV3Version>1.1.0-pre.3</XunitV3Version>
15+
<XunitV3Version>1.1.0-pre.11</XunitV3Version>
1616
</PropertyGroup>
1717

1818
</Project>

src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs

+93-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Text;
47
using System.Threading;
8+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
59
using Xunit.Runner.Common;
610
using Xunit.Sdk;
711
using VsTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase;
@@ -16,6 +20,8 @@ namespace Xunit.Runner.VisualStudio;
1620

1721
internal sealed class VsExecutionSink : TestMessageSink, IDisposable
1822
{
23+
static readonly HashSet<char> InvalidFileNameChars = Path.GetInvalidFileNameChars().ToHashSet();
24+
1925
readonly Func<bool> cancelledThunk;
2026
readonly LoggerHelper logger;
2127
readonly IMessageSink innerSink;
@@ -24,10 +30,12 @@ internal sealed class VsExecutionSink : TestMessageSink, IDisposable
2430
readonly ConcurrentDictionary<string, DateTimeOffset> startTimeByTestID = [];
2531
readonly ConcurrentDictionary<string, List<ITestCaseStarting>> testCasesByAssemblyID = [];
2632
readonly ConcurrentDictionary<string, ITestCaseStarting> testCasesByCaseID = [];
33+
readonly ConcurrentDictionary<string, (string actionDescription, ITestMessage test, VsTestResult testResult)> testResultByCaseID = [];
2734
readonly ConcurrentDictionary<string, List<ITestCaseStarting>> testCasesByClassID = [];
2835
readonly ConcurrentDictionary<string, List<ITestCaseStarting>> testCasesByCollectionID = [];
2936
readonly ConcurrentDictionary<string, List<ITestCaseStarting>> testCasesByMethodID = [];
3037
readonly Dictionary<string, VsTestCase> testCasesMap;
38+
static readonly Uri uri = new(Constants.ExecutorUri);
3139

3240
public VsExecutionSink(
3341
IMessageSink innerSink,
@@ -298,16 +306,77 @@ void HandleTestFailed(MessageHandlerArgs<ITestFailed> args)
298306
result.ErrorMessage = ExceptionUtility.CombineMessages(testFailed);
299307
result.ErrorStackTrace = ExceptionUtility.CombineStackTraces(testFailed);
300308

301-
TryAndReport("RecordResult (Fail)", testFailed, () => recorder.RecordResult(result));
309+
DeferReportUntilTestFinished("RecordResult (Fail)", testFailed, result);
302310
}
303311
else
304312
LogWarning(testFailed, "(Fail) Could not find VS test case for {0} (ID = {1})", TestDisplayName(testFailed), testFailed.TestCaseUniqueID);
305313

306314
HandleCancellation(args);
307315
}
308316

309-
void HandleTestFinished(MessageHandlerArgs<ITestFinished> args) =>
317+
void HandleTestFinished(MessageHandlerArgs<ITestFinished> args)
318+
{
319+
var testUniqueID = args.Message.TestUniqueID;
320+
321+
if (testResultByCaseID.TryRemove(testUniqueID, out var testResultEntry))
322+
{
323+
var (actionDescription, test, testResult) = testResultEntry;
324+
var attachments = args.Message.Attachments;
325+
326+
if (attachments.Count != 0)
327+
try
328+
{
329+
var basePath = Path.Combine(Path.GetTempPath(), testUniqueID);
330+
Directory.CreateDirectory(basePath);
331+
332+
var attachmentSet = new AttachmentSet(uri, "xUnit.net");
333+
334+
foreach (var kvp in attachments)
335+
{
336+
var localFilePath = Path.Combine(basePath, SanitizeFileName(kvp.Key));
337+
338+
try
339+
{
340+
var attachmentType = kvp.Value.AttachmentType;
341+
342+
if (attachmentType == TestAttachmentType.String)
343+
{
344+
localFilePath += ".txt";
345+
File.WriteAllText(localFilePath, kvp.Value.AsString());
346+
}
347+
else if (attachmentType == TestAttachmentType.ByteArray)
348+
{
349+
var (byteArray, mediaType) = kvp.Value.AsByteArray();
350+
localFilePath += MediaTypeUtility.GetFileExtension(mediaType);
351+
File.WriteAllBytes(localFilePath, byteArray);
352+
}
353+
else
354+
{
355+
LogWarning(test, "Unknown test attachment type '{0}' for attachment '{1}' [test case ID '{2}']", attachmentType, kvp.Key, testUniqueID);
356+
localFilePath = null;
357+
}
358+
359+
if (localFilePath is not null)
360+
attachmentSet.Attachments.Add(UriDataAttachment.CreateFrom(localFilePath, kvp.Key));
361+
}
362+
catch (Exception ex)
363+
{
364+
LogWarning(test, "Exception while adding attachment '{0}' in '{1}' [test case ID '{2}']: {3}", kvp.Key, localFilePath, testUniqueID, ex);
365+
}
366+
}
367+
368+
testResult.Attachments.Add(attachmentSet);
369+
}
370+
catch (Exception ex)
371+
{
372+
LogWarning(test, "Exception while adding attachments [test case ID '{0}']: {1}", testUniqueID, ex);
373+
}
374+
375+
TryAndReport(actionDescription, test, () => recorder.RecordResult(testResult));
376+
}
377+
310378
MetadataCache(args.Message)?.TryRemove(args.Message);
379+
}
311380

312381
void HandleTestMethodCleanupFailure(MessageHandlerArgs<ITestMethodCleanupFailure> args)
313382
{
@@ -340,7 +409,7 @@ void HandleTestNotRun(MessageHandlerArgs<ITestNotRun> args)
340409

341410
var result = MakeVsTestResult(VsTestOutcome.None, testNotRun, startTime);
342411
if (result is not null)
343-
TryAndReport("RecordResult (None)", testNotRun, () => recorder.RecordResult(result));
412+
DeferReportUntilTestFinished("RecordResult (None)", testNotRun, result);
344413
else
345414
LogWarning(testNotRun, "(NotRun) Could not find VS test case for {0} (ID = {1})", TestDisplayName(testNotRun), testNotRun.TestCaseUniqueID);
346415

@@ -354,7 +423,7 @@ void HandleTestPassed(MessageHandlerArgs<ITestPassed> args)
354423

355424
var result = MakeVsTestResult(VsTestOutcome.Passed, testPassed, startTime);
356425
if (result is not null)
357-
TryAndReport("RecordResult (Pass)", testPassed, () => recorder.RecordResult(result));
426+
DeferReportUntilTestFinished("RecordResult (Pass)", testPassed, result);
358427
else
359428
LogWarning(testPassed, "(Pass) Could not find VS test case for {0} (ID = {1})", TestDisplayName(testPassed), testPassed.TestCaseUniqueID);
360429

@@ -368,7 +437,7 @@ void HandleTestSkipped(MessageHandlerArgs<ITestSkipped> args)
368437

369438
var result = MakeVsTestResult(VsTestOutcome.Skipped, testSkipped, startTime);
370439
if (result is not null)
371-
TryAndReport("RecordResult (Skip)", testSkipped, () => recorder.RecordResult(result));
440+
DeferReportUntilTestFinished("RecordResult (Skip)", testSkipped, result);
372441
else
373442
LogWarning(testSkipped, "(Skip) Could not find VS test case for {0} (ID = {1})", TestDisplayName(testSkipped), testSkipped.TestCaseUniqueID);
374443

@@ -494,6 +563,25 @@ string TestDisplayName(ITestMessage msg) =>
494563
string TestMethodName(ITestMethodMessage msg) =>
495564
TestClassName(msg) + "." + MetadataCache(msg)?.TryGetMethodMetadata(msg)?.MethodName ?? $"<unknown test method ID {msg.TestMethodUniqueID}>";
496565

566+
void DeferReportUntilTestFinished(
567+
string actionDescription,
568+
ITestMessage test,
569+
VsTestResult testResult) =>
570+
testResultByCaseID.TryAdd(test.TestUniqueID, (actionDescription, test, testResult));
571+
572+
string SanitizeFileName(string fileName)
573+
{
574+
var result = new StringBuilder(fileName.Length);
575+
576+
foreach (var c in fileName)
577+
if (InvalidFileNameChars.Contains(c))
578+
result.Append('_');
579+
else
580+
result.Append(c);
581+
582+
return result.ToString();
583+
}
584+
497585
void TryAndReport(
498586
string actionDescription,
499587
ITestCaseMessage testCase,

test/Directory.Build.props

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
<PropertyGroup>
55
<DebugSymbols>true</DebugSymbols>
66
<DebugType>portable</DebugType>
7+
<!-- Never use Microsoft.Testing.Platform mode, as that bypasses the VSTest adapter -->
8+
<DisableTestingPlatformServerCapability>true</DisableTestingPlatformServerCapability>
79
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
810
<LangVersion>12.0</LangVersion>
911
<PackageId>$(MSBuildProjectName)</PackageId>

0 commit comments

Comments
 (0)