Skip to content

Commit

Permalink
Back-fill tests for VsDiscoverySink
Browse files Browse the repository at this point in the history
  • Loading branch information
bradwilson committed Oct 30, 2024
1 parent e2d5be2 commit 3d3b241
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 48 deletions.
53 changes: 5 additions & 48 deletions src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
Expand All @@ -22,7 +21,6 @@ public sealed class VsDiscoverySink : IVsDiscoverySink, IDisposable
const int MaximumDisplayNameLength = 447;
const int TestCaseBatchSize = 100;

static readonly Action<VsTestCase, string, string>? addTraitThunk = GetAddTraitThunk();
static readonly Uri uri = new(Constants.ExecutorUri);

readonly Func<bool> cancelThunk;
Expand Down Expand Up @@ -71,7 +69,7 @@ public void Dispose() =>
{
if (testCase.TestClassName is null)
{
logger.LogErrorWithSource(source, "Error creating Visual Studio test case for {0}: TestClassWithNamespace is null", testCase.TestCaseDisplayName);
logger.LogErrorWithSource(source, "Error creating Visual Studio test case for {0}: TestClassName is null", testCase.TestCaseDisplayName);
return null;
}

Expand Down Expand Up @@ -103,14 +101,10 @@ public void Dispose() =>
result.CodeFilePath = testCase.SourceFilePath;
result.LineNumber = testCase.SourceLineNumber.GetValueOrDefault();

if (addTraitThunk is not null)
{
var traits = testCase.Traits;

foreach (var key in traits.Keys)
foreach (var value in traits[key])
addTraitThunk(result, key, value);
}
var traits = testCase.Traits;
foreach (var key in traits.Keys)
foreach (var value in traits[key])
result.Traits.Add(key, value);

return result;
}
Expand Down Expand Up @@ -143,43 +137,6 @@ public int Finish()
return TotalTests;
}

static Action<VsTestCase, string, string>? GetAddTraitThunk()
{
try
{
var testCaseType = typeof(VsTestCase);
var stringType = typeof(string);

#if NETCOREAPP
var property = testCaseType.GetRuntimeProperty("Traits");
#else
var property = testCaseType.GetProperty("Traits");
#endif
if (property is null)
return null;

#if NETCOREAPP
var method = property.PropertyType.GetRuntimeMethod("Add", [typeof(string), typeof(string)]);
#else
var method = property.PropertyType.GetMethod("Add", [typeof(string), typeof(string)]);
#endif
if (method is null)
return null;

var thisParam = Expression.Parameter(testCaseType, "this");
var nameParam = Expression.Parameter(stringType, "name");
var valueParam = Expression.Parameter(stringType, "value");
var instance = Expression.Property(thisParam, property);
var body = Expression.Call(instance, method, [nameParam, valueParam]);

return Expression.Lambda<Action<VsTestCase, string, string>>(body, thisParam, nameParam, valueParam).Compile();
}
catch (Exception)
{
return null;
}
}

void HandleCancellation(MessageHandlerArgs args)
{
if (cancelThunk())
Expand Down
85 changes: 85 additions & 0 deletions test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
using Xunit.Runner.VisualStudio;

public class VsDiscoverySinkTests
{
public class CreateVsTestCase
{
readonly SpyLoggerHelper logger = SpyLoggerHelper.Create();
readonly TestPlatformContext testPlatformContext = new TestPlatformContext { DesignMode = false };

[Fact]
public void MustSetTestClassName()
{
var testCase = TestData.TestCaseDiscovered(testClassName: null);

var vsTestCase = VsDiscoverySink.CreateVsTestCase("source", testCase, logger, testPlatformContext);

Assert.Null(vsTestCase);
var message = Assert.Single(logger.Messages);
Assert.Equal("[Error] [xUnit.net 00:00:00.00] source: Error creating Visual Studio test case for test-case-display-name: TestClassName is null", message);
}

[Theory]
[InlineData(false, null)]
[InlineData(true, "serialization")]
public void StandardData(
bool designMode,
string? expectedSerialization)
{
var testCase = TestData.TestCaseDiscovered(
sourceFilePath: "/source/file.cs",
sourceLineNumber: 42,
traits: new Dictionary<string, IReadOnlyCollection<string>>
{
{ "foo", ["baz", "bar"] },
{ "biff", ["42"] },
}
);
var testPlatformContext = new TestPlatformContext { DesignMode = designMode };

var vsTestCase = VsDiscoverySink.CreateVsTestCase("source", testCase, logger, testPlatformContext);

Assert.NotNull(vsTestCase);

// Standard VSTest properties
Assert.Equal("/source/file.cs", vsTestCase.CodeFilePath);
Assert.Equal("test-case-display-name", vsTestCase.DisplayName);
Assert.Equal(Constants.ExecutorUri, vsTestCase.ExecutorUri.OriginalString);
Assert.Equal("test-class-name.test-method", vsTestCase.FullyQualifiedName);
Assert.NotEqual(Guid.Empty, vsTestCase.Id); // Computed at runtime, just need to ensure it's set
Assert.Equal(42, vsTestCase.LineNumber);
Assert.Equal("source", vsTestCase.Source);
Assert.Collection(
vsTestCase.Traits.Select(t => $"'{t.Name}' = '{t.Value}'").OrderBy(x => x),
trait => Assert.Equal("'biff' = '42'", trait),
trait => Assert.Equal("'foo' = 'bar'", trait),
trait => Assert.Equal("'foo' = 'baz'", trait)
);

// xUnit.net extension properties
Assert.Equal(expectedSerialization, vsTestCase.GetPropertyValue(VsTestRunner.TestCaseSerializationProperty));
Assert.Equal("test-case-id", vsTestCase.GetPropertyValue(VsTestRunner.TestCaseUniqueIDProperty));
Assert.Equal(false, vsTestCase.GetPropertyValue(VsTestRunner.TestCaseExplicitProperty));
}

[Theory]
[InlineData(null, "test-method")]
[InlineData(new[] { "Type1", "Type2" }, "test-method(Type1,Type2)")]
public void SetsManagedTypeAndMethodProperties(
string[]? parameterTypes,
string expectedManagedMethodName)
{
var testCase = TestData.TestCaseDiscovered(testMethodParameterTypes: parameterTypes);

var vsTestCase = VsDiscoverySink.CreateVsTestCase("source", testCase, logger, testPlatformContext);

Assert.NotNull(vsTestCase);
Assert.Equal("test-class-name", vsTestCase.GetPropertyValue(VsTestRunner.ManagedTypeProperty));
Assert.Equal(expectedManagedMethodName, vsTestCase.GetPropertyValue(VsTestRunner.ManagedMethodProperty));
}
}
}
14 changes: 14 additions & 0 deletions test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;

namespace Xunit.Runner.VisualStudio;

public class SpyLoggerHelper(SpyMessageLogger logger, Stopwatch stopwatch) :
LoggerHelper(logger, stopwatch)
{
public IReadOnlyCollection<string> Messages => logger.Messages;

public static SpyLoggerHelper Create() =>
new(new(), new());
}
13 changes: 13 additions & 0 deletions test/test.xunit.runner.visualstudio/Utility/SpyMessageLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Collections.Generic;

namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;

public class SpyMessageLogger : IMessageLogger
{
public readonly List<string> Messages = [];

public void SendMessage(
TestMessageLevel testMessageLevel,
string message) =>
Messages.Add($"[{testMessageLevel}] {message}");
}
53 changes: 53 additions & 0 deletions test/test.xunit.runner.visualstudio/Utility/TestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using Xunit.Runner.Common;
using Xunit.Sdk;

internal static class TestData
{
static readonly IReadOnlyDictionary<string, IReadOnlyCollection<string>> EmptyTraits = new Dictionary<string, IReadOnlyCollection<string>>();

public static ITestCaseDiscovered TestCaseDiscovered(
string assemblyUniqueID = "assembly-id",
bool @explicit = false,
string serialization = "serialization",
string? skipReason = null,
string? sourceFilePath = null,
int? sourceLineNumber = null,
string testCaseDisplayName = "test-case-display-name",
string testCaseUniqueID = "test-case-id",
int? testClassMetadataToken = null,
string? testClassName = "test-class-name",
string? testClassNamespace = null,
string? testClassSimpleName = "test-class-simple-name",
string? testClassUniqueID = "test-class-id",
string testCollectionUniqueID = "test-collection-id",
int? testMethodMetadataToken = null,
string? testMethodName = "test-method",
string[]? testMethodParameterTypes = null,
string? testMethodReturnType = null,
string? testMethodUniqueID = "test-method-id",
IReadOnlyDictionary<string, IReadOnlyCollection<string>>? traits = null) =>
new TestCaseDiscovered
{
AssemblyUniqueID = assemblyUniqueID,
Explicit = @explicit,
Serialization = serialization,
SkipReason = skipReason,
SourceFilePath = sourceFilePath,
SourceLineNumber = sourceLineNumber,
TestCaseDisplayName = testCaseDisplayName,
TestCaseUniqueID = testCaseUniqueID,
TestClassMetadataToken = testClassMetadataToken,
TestClassName = testClassName,
TestClassNamespace = testClassNamespace,
TestClassSimpleName = testClassSimpleName,
TestClassUniqueID = testClassUniqueID,
TestCollectionUniqueID = testCollectionUniqueID,
TestMethodMetadataToken = testMethodMetadataToken,
TestMethodName = testMethodName,
TestMethodParameterTypesVSTest = testMethodParameterTypes,
TestMethodReturnTypeVSTest = testMethodReturnType,
TestMethodUniqueID = testMethodUniqueID,
Traits = traits ?? EmptyTraits,
};
}

0 comments on commit 3d3b241

Please sign in to comment.