Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1bbfc9c
Add a basic xunit runner generator that generates a top-level-stateme…
jkoritzinsky Oct 22, 2021
6523a11
Fix some bugs in the generator. Convert the Interop/PInvoke/Vector2_3…
jkoritzinsky Oct 22, 2021
6143a48
Implement support for [Fact] methods that are instance methods on IDi…
jkoritzinsky Oct 22, 2021
9b9ae95
Import enum sources from XUnitExtensions and hook up support for some…
jkoritzinsky Oct 25, 2021
54e93eb
Add one more newline
jkoritzinsky Oct 25, 2021
14ca067
Add preliminary support for building an IL test runner using the sour…
jkoritzinsky Oct 25, 2021
149ffe2
Rename to make the logic clearer.
jkoritzinsky Oct 25, 2021
23b7975
Update src/tests/Common/XUnitWrapperGenerator/XUnitWrapperGenerator.cs
jkoritzinsky Oct 25, 2021
623cc48
Add support for extern-alias with references (to handle conflicting n…
jkoritzinsky Oct 26, 2021
a20864e
Apply suggestions from code review
jkoritzinsky Nov 2, 2021
4a7ebe9
Merge branch 'main' of github.com:dotnet/runtime into xunit-runner-ge…
jkoritzinsky Nov 2, 2021
f5009c8
Update src/tests/Common/XUnitWrapperGenerator/XUnitWrapperGenerator.cs
jkoritzinsky Nov 2, 2021
59960aa
Add the XUnit wrapper generator to the list of ambient project depend…
trylek Nov 2, 2021
191ac42
Fix nullability bug in XUnitWrapperGenerator
trylek Nov 2, 2021
a2bdb78
Merge branch 'main' of github.com:dotnet/runtime into xunit-runner-ge…
jkoritzinsky Nov 3, 2021
e597fec
Merge branch 'main' of github.com:dotnet/runtime into xunit-runner-ge…
jkoritzinsky Nov 8, 2021
e783f9e
Write out the exception that caused the test to fail to the console.
jkoritzinsky Nov 8, 2021
c978193
Convert the Castable test to use the generator
jkoritzinsky Nov 8, 2021
270144b
Support running the xunit wrapper generator in "standalone" mode for …
jkoritzinsky Nov 8, 2021
4c73d6f
Add first draft of result reporting infrastructure.
jkoritzinsky Nov 8, 2021
e81deda
Fix IL .assembly directive to be the assembly name that can be resolv…
jkoritzinsky Nov 9, 2021
4f9dcbb
Add test reporting for "merged test runner" assemblies.
jkoritzinsky Nov 10, 2021
d5961f6
Add Microsoft.DotNet.XUnitExtensions to the targetting pack.
jkoritzinsky Nov 10, 2021
4d412d0
Add support for the rest of ActiveIssueAttribute
jkoritzinsky Nov 10, 2021
377b8a0
Add support for more attributes and clean up code.
jkoritzinsky Nov 10, 2021
835f56d
Add support for SkipOnCoreClrAttribute.
jkoritzinsky Nov 10, 2021
c84c5d1
Merge branch 'main' of github.com:dotnet/runtime into xunit-runner-ge…
jkoritzinsky Nov 10, 2021
b7fb243
Support running tests with the [Fact] attribute that return 100 for s…
jkoritzinsky Nov 10, 2021
d49c1f4
Fix failures and update comment
jkoritzinsky Nov 10, 2021
1b19bed
Update src/tests/Common/XUnitWrapperGenerator/ITestInfo.cs
jkoritzinsky Nov 11, 2021
040877c
Fix handling of SkipOnCoreClrAttribute and SkipOnMonoAttribute with R…
jkoritzinsky Nov 11, 2021
d736995
Add support for running tests marked with <RequiresProcessIsolation>t…
jkoritzinsky Nov 12, 2021
a77d13c
Merge branch 'main' into xunit-runner-generator
jkoritzinsky Nov 12, 2021
04650fd
Add support for substring-based test filtering and add infrastructure…
jkoritzinsky Nov 15, 2021
8ef73bd
Integrate work from #61224 with some modifications based on offline d…
jkoritzinsky Nov 15, 2021
5881fa1
Fix Castable.csproj and fix SkipOnMono/SkipOnCoreCLR attribute support.
jkoritzinsky Nov 15, 2021
eccf29a
Work around tests that hit compiler limits. Move tests that used Xuni…
jkoritzinsky Nov 16, 2021
ccd08a2
Rewrite how superpmicollect sets up its testing to make it more frien…
jkoritzinsky Nov 16, 2021
db68ce4
Add back the _BuildSpmiTestProjectScripts target since I'm not sure w…
jkoritzinsky Nov 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add test reporting for "merged test runner" assemblies.
  • Loading branch information
jkoritzinsky committed Nov 10, 2021
commit 4f9dcbbe4d2c3cef3ec08fc0b0574cab1fd0ac38
9 changes: 0 additions & 9 deletions src/tests/Common/XUnitWrapperGenerator/Constants.cs

This file was deleted.

156 changes: 129 additions & 27 deletions src/tests/Common/XUnitWrapperGenerator/ITestInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,102 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;

namespace XUnitWrapperGenerator;

interface ITestInfo
{
string ExecutionStatement { get; }
string TestNameExpression { get; }
string Method { get; }
string ContainingType { get; }

string GenerateTestExecution(ITestReporterWrapper testReporterWrapper);
}

interface ITestReporterWrapper
{
string WrapTestExecutionWithReporting(string testExecution, ITestInfo test);

string GenerateSkippedTestReporting(ITestInfo skippedTest);
}

sealed class BasicTestMethod : ITestInfo
{
public BasicTestMethod(IMethodSymbol method, string externAlias, ImmutableArray<string> arguments = default)
public BasicTestMethod(IMethodSymbol method, string externAlias, ImmutableArray<string> arguments = default, string? displayNameExpression = null)
{
var args = arguments.IsDefaultOrEmpty ? "" : string.Join(", ", arguments);
string containingType = method.ContainingType.ToDisplayString(XUnitWrapperGenerator.FullyQualifiedWithoutGlobalNamespace);
ContainingType = method.ContainingType.ToDisplayString(XUnitWrapperGenerator.FullyQualifiedWithoutGlobalNamespace);
Method = method.Name;
TestNameExpression = displayNameExpression ?? $"\"{externAlias}::{ContainingType}.{Method}({args})\"";
if (method.IsStatic)
{
ExecutionStatement = $"{externAlias}::{containingType}.{method.Name}({args});";
ExecutionStatement = $"{externAlias}::{ContainingType}.{Method}({args});";
}
else
{
ExecutionStatement = $"using ({externAlias}::{containingType} obj = new()) obj.{method.Name}({args});";
ExecutionStatement = $"using ({externAlias}::{ContainingType} obj = new()) obj.{Method}({args});";
}
}

public string ExecutionStatement { get; }
public string TestNameExpression { get; }
public string Method { get; }
public string ContainingType { get; }
private string ExecutionStatement { get; }

public string GenerateTestExecution(ITestReporterWrapper testReporterWrapper)
{
return testReporterWrapper.WrapTestExecutionWithReporting(ExecutionStatement, this);
}

public override bool Equals(object obj)
{
return obj is BasicTestMethod other && ExecutionStatement == other.ExecutionStatement;
return obj is BasicTestMethod other
&& TestNameExpression == other.TestNameExpression
&& Method == other.Method
&& ContainingType == other.ContainingType;;
}
}

sealed class ConditionalTest : ITestInfo
{
private ITestInfo _innerTest;
private string _condition;
public ConditionalTest(ITestInfo innerTest, string condition)
{
ExecutionStatement = $"if ({condition}) {{ {innerTest.ExecutionStatement} }}";
_innerTest = innerTest;
_condition = condition;
TestNameExpression = innerTest.TestNameExpression;
Method = innerTest.Method;
ContainingType = innerTest.ContainingType;
}

public ConditionalTest(ITestInfo innerTest, Xunit.TestPlatforms platform)
: this(innerTest, GetPlatformConditionFromTestPlatform(platform))
{
}

public string ExecutionStatement { get; }
public string TestNameExpression { get; }
public string Method { get; }
public string ContainingType { get; }

public string GenerateTestExecution(ITestReporterWrapper testReporterWrapper)
{
return $"if ({_condition}) {{ {_innerTest.GenerateTestExecution(testReporterWrapper)} }} else {{ {testReporterWrapper.GenerateSkippedTestReporting(_innerTest)} }}";
}

public override bool Equals(object obj)
{
return obj is ConditionalTest other && ExecutionStatement == other.ExecutionStatement;
return obj is ConditionalTest other
&& TestNameExpression == other.TestNameExpression
&& Method == other.Method
&& ContainingType == other.ContainingType
&& _condition == other._condition
&& _innerTest.Equals(other._innerTest);
}
}

sealed class PlatformSpecificTest : ITestInfo
{
public PlatformSpecificTest(ITestInfo innerTest, Xunit.TestPlatforms platform)
private static string GetPlatformConditionFromTestPlatform(Xunit.TestPlatforms platform)
{
List<string> platformCheckConditions = new();
if (platform.HasFlag(Xunit.TestPlatforms.Windows))
Expand Down Expand Up @@ -105,35 +150,92 @@ public PlatformSpecificTest(ITestInfo innerTest, Xunit.TestPlatforms platform)
{
platformCheckConditions.Add(@"global::System.OperatingSystem.IsOSPlatform(""NetBSD"")");
}
ExecutionStatement = $"if ({string.Join(" || ", platformCheckConditions)}) {{ {innerTest.ExecutionStatement} }}";
}

public string ExecutionStatement { get; }

public override bool Equals(object obj)
{
return obj is PlatformSpecificTest other && ExecutionStatement == other.ExecutionStatement;
return string.Join(" || ", platformCheckConditions);
}
}

sealed class MemberDataTest : ITestInfo
{
private ITestInfo _innerTest;
private string _memberInvocation;
private string _loopVarIdentifier;
public MemberDataTest(ISymbol referencedMember, ITestInfo innerTest, string externAlias, string argumentLoopVarIdentifier)
{
TestNameExpression = innerTest.TestNameExpression;
Method = innerTest.Method;
ContainingType = innerTest.ContainingType;
_innerTest = innerTest;
_loopVarIdentifier = argumentLoopVarIdentifier;

string containingType = referencedMember.ContainingType.ToDisplayString(XUnitWrapperGenerator.FullyQualifiedWithoutGlobalNamespace);
string memberInvocation = referencedMember switch
_memberInvocation = referencedMember switch
{
IPropertySymbol { IsStatic: true } => $"{externAlias}::{containingType}.{referencedMember.Name}",
IMethodSymbol { IsStatic: true, Parameters: { Length: 0 } } => $"{externAlias}::{containingType}.{referencedMember.Name}()",
_ => throw new ArgumentException()
};
ExecutionStatement = $@"
foreach (object[] {argumentLoopVarIdentifier} in {memberInvocation})
}

public string TestNameExpression { get; }
public string Method { get; }
public string ContainingType { get; }

public string GenerateTestExecution(ITestReporterWrapper testReporterWrapper)
{
return $@"
foreach (object[] {_loopVarIdentifier} in {_memberInvocation})
{{
{innerTest.ExecutionStatement}
{_innerTest.GenerateTestExecution(testReporterWrapper)}
}}
";
}

public string ExecutionStatement { get; }
public override bool Equals(object obj)
{
return obj is MemberDataTest other
&& TestNameExpression == other.TestNameExpression
&& Method == other.Method
&& ContainingType == other.ContainingType
&& _innerTest.Equals(other._innerTest);
}
}

class NoTestReporting : ITestReporterWrapper
{
public string WrapTestExecutionWithReporting(string testExecution, ITestInfo test) => testExecution;

public string GenerateSkippedTestReporting(ITestInfo skippedTest) => string.Empty;
}

class WrapperLibraryTestSummaryReporting : ITestReporterWrapper
{
private string _summaryLocalIdentifier;

public WrapperLibraryTestSummaryReporting(string summaryLocalIdentifier)
{
_summaryLocalIdentifier = summaryLocalIdentifier;
}

public string WrapTestExecutionWithReporting(string testExecutionExpression, ITestInfo test)
{
StringBuilder builder = new();
builder.AppendLine("{");

builder.AppendLine($"TimeSpan testStart = stopwatch.Elapsed;");
builder.AppendLine("try {");
builder.AppendLine(testExecutionExpression);
builder.AppendLine($"{_summaryLocalIdentifier}.ReportPassedTest({test.TestNameExpression}, \"{test.ContainingType}\", \"{test.Method}\", stopwatch.Elapsed - testStart);");
builder.AppendLine("}");
builder.AppendLine("catch (System.Exception ex) {");
builder.AppendLine($"{_summaryLocalIdentifier}.ReportFailedTest({test.TestNameExpression}, \"{test.ContainingType}\", \"{test.Method}\", stopwatch.Elapsed - testStart, ex);");
builder.AppendLine("}");

builder.AppendLine("}");
return builder.ToString();
}

public string GenerateSkippedTestReporting(ITestInfo skippedTest)
{
return $"{_summaryLocalIdentifier}.ReportSkippedTest({skippedTest.TestNameExpression}, \"{skippedTest.ContainingType}\", \"{skippedTest.Method}\", stopwatch.Elapsed - testStart, string.Empty);";
}
}
27 changes: 9 additions & 18 deletions src/tests/Common/XUnitWrapperGenerator/XUnitWrapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,26 +81,15 @@ private static string GenerateFullTestRunner(ImmutableArray<ITestInfo> testInfos
{
// For simplicity, we'll use top-level statements for the generated Main method.
StringBuilder builder = new();
ITestReporterWrapper reporter = new WrapperLibraryTestSummaryReporting("summary");
builder.AppendLine(string.Join("\n", aliasMap.Values.Where(alias => alias != "global").Select(alias => $"extern alias {alias};")));

builder.AppendLine("XUnitWrapperLibrary.TestSummary summary = new();");
builder.AppendLine("System.Diagnostics.Stopwatch stopwatch = new();");

foreach (ITestInfo test in testInfos)
{
builder.AppendLine("{");

builder.AppendLine($"TimeSpan testStart = stopwatch.Elapsed;");
builder.AppendLine("try {");
builder.AppendLine($"bool {Constants.TestResultReportedIdentifier} = false;");
builder.AppendLine(test.ExecutionStatement);
builder.AppendLine($"if (!{Constants.TestResultReportedIdentifier}) summary.ReportPassedTest(null, null, null, stopwatch.Elapsed - testStart);");
builder.AppendLine("}");
builder.AppendLine("catch (System.Exception ex) {");
builder.AppendLine($"summary.ReportFailedTest(null, null, null, stopwatch.Elapsed - testStart, ex);");
builder.AppendLine("}");

builder.AppendLine("}");
builder.AppendLine(test.GenerateTestExecution(reporter));
}

builder.AppendLine($@"System.IO.File.WriteAllText(""{assemblyName}.testResults.xml"", summary.GetTestResultOutput());");
Expand All @@ -111,10 +100,11 @@ private static string GenerateFullTestRunner(ImmutableArray<ITestInfo> testInfos
private static string GenerateStandaloneSimpleTestRunner(ImmutableArray<ITestInfo> testInfos, ImmutableDictionary<string, string> aliasMap, string consoleType)
{
// For simplicity, we'll use top-level statements for the generated Main method.
ITestReporterWrapper reporter = new NoTestReporting();
StringBuilder builder = new();
builder.AppendLine(string.Join("\n", aliasMap.Values.Where(alias => alias != "global").Select(alias => $"extern alias {alias};")));
builder.AppendLine("try {");
builder.AppendLine(string.Join("\n", testInfos.Select(m => m.ExecutionStatement)));
builder.AppendLine(string.Join("\n", testInfos.Select(m => m.GenerateTestExecution(reporter))));
builder.AppendLine($"}} catch(System.Exception ex) {{ {consoleType}.WriteLine(ex.ToString()); return 101; }}");
builder.AppendLine("return 100;");
return builder.ToString();
Expand Down Expand Up @@ -300,10 +290,11 @@ private static ImmutableArray<ITestInfo> CreateTestCases(IMethodSymbol method, L
// Emit diagnostic
continue;
}
int numArguments = method.Parameters.Length;
const string argumentVariableIdentifier = "testArguments";
var argsAsCode = Enumerable.Range(0, numArguments).Select(i => $"{argumentVariableIdentifier}[{i}]").ToImmutableArray();
testCasesBuilder.Add(new MemberDataTest(membersByName[0], new BasicTestMethod(method, alias, argsAsCode), alias, argumentVariableIdentifier));
// The display name for the test is an interpolated string that includes the arguments.
string displayNameOverride = $@"$""{alias}::{method.ContainingType.ToDisplayString(FullyQualifiedWithoutGlobalNamespace)}.{method.Name}({{string.Join("","", {argumentVariableIdentifier})}})""";
var argsAsCode = method.Parameters.Select((p, i) => $"({p.Type.ToDisplayString()}){argumentVariableIdentifier}[{i}]").ToImmutableArray();
testCasesBuilder.Add(new MemberDataTest(membersByName[0], new BasicTestMethod(method, alias, argsAsCode, displayNameOverride), alias, argumentVariableIdentifier));
break;
}
default:
Expand Down Expand Up @@ -344,7 +335,7 @@ private static ImmutableArray<ITestInfo> DecorateWithSkipOnPlatform(ImmutableArr
// If our target platform encompases one or more of the skipped platforms,
// emit a runtime platform check here.
Xunit.TestPlatforms platformsToEnableTest = targetPlatform & ~platformsToSkip;
return ImmutableArray.CreateRange(testInfos.Select(t => (ITestInfo)new PlatformSpecificTest(t, platformsToEnableTest)));
return ImmutableArray.CreateRange(testInfos.Select(t => (ITestInfo)new ConditionalTest(t, platformsToEnableTest)));
}
else
{
Expand Down